You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2026/01/22 14:08:24 UTC
(nifi) branch NIFI-15258 updated (b515661ba4 -> 135d3f0d70)
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a change to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
omit b515661ba4 NIFI-15461: Added ability to initiate drainage of Connector's FlowFiles and calce; added tests to verify; some bug fixes (#10767)
omit 673b493e2c NIFI-15481: Updated ConnectorActions to reflect that starting is not … (#10782)
omit 9c9e369676 NIFI-15451: Added ability for Connectors to retrieve bundles availabl… (#10756)
omit fcd3d09a3b NIFI-15482: MockConnectorAssetManager requires SPI file to start (#10783)
omit 5499bc7fb6 NIFI-15472: Added addAsset methods to ConnectorTestRunner (#10776)
omit 8b56c99281 NIFI-15453: Adding an endpoint to return all controller services for … (#10770)
omit 57d025f52c NIFI-15376: Adding dependencies to the configuration step dto. (#10674)
omit 5bed6fd393 NIFI-15446: When invoking ConnectorMethod, make sure to serialize/des… (#10750)
omit 00ad24bd47 NIFI-15468: Ensure that we properly initialize ConnectorNode even if a Ghost Connector is created (#10775)
omit 9d448caa1f NIFI-15439: Ensure that Process Groups are accessible from the approp… (#10742)
omit bb8aa894f1 NIFI-15440: Implementation of ConnectorActions (#10748)
omit 9366f351ac NIFI-15445: Removed 'disabled' state from Connectors; also fixed bug … (#10749)
omit 4053a81c4c NIFI-15434: Ensure that we start or stop connectors on flow sync (#10737)
omit 3288c0dbfd NIFI-15433: If connector validation throws an Exception keep trying u… (#10736)
omit 8890b4230e NIFI-15427: Added abiliy to drop flowfiles / drain flowfiles from a C… (#10730)
omit 73a0dec49c NIFI-15430: Ensure that if we fail to initialize a Connector, we crea… (#10733)
omit c9dde94147 NIFI-15429: Adding an optional query parameter for specifying which p… (#10732)
omit 8c35327eb1 NIFI-15353: Adding support for rendering Connector Documentation. (#10683)
omit 0a861bea8b NIFI-15356: Adding authorization to the StandardNiFiConnectorWebContext. (#10660)
omit 805cc8b039 NIFI-15370: Add Connector ID to process groups and do not register create process groups if they have a connector ID. (#10673)
omit 7573b64e24 NIFI-15367: Ensure that Connectors' implicit parameter contexts are n… (#10665)
omit 2baec28fcc NIFI-15369: Allow ConfigurationStep to depend on another (ConfigurationStep,Property) tuple (#10669)
omit 60efb80c1d NIFI-15361: Allowing configuration step documentation to be returned … (#10667)
omit 2d120a1222 NIFI-15315 Add support for assets in connectors (#10647)
omit d2c8b77c51 NIFI-15343: Adding an endpoint to return available secrets to the con… (#10645)
omit c1342113db NIFI-15352: Added ConnectorClient to toolkit-cli so that it can be us… (#10654)
omit f1075f8c83 NIFI-15258: Enable Custom UI to retrieve connector with flow contexts (#10652)
omit 60d1ece5de NIFI-15336: Created AuthorizableSecret and implemented ParameterProviderSecretProvider to provide the Parameter Provider as the associated Authorizable (#10644)
omit 292d27d888 NIFI-15312: Implementation of Parameter Provider based Secrets Manager; updated mock framework to support secrets; validation cleanup (#10638)
omit 9cd672c812 NIFI-15330: Allow GhostConnector to be updated (#10632)
omit 7438c9926b NIFI-15326: Adding support of configuration step documentation. (#10631)
omit 04f3b44b03 NIFI-15322: Require all property descriptors within a PropertyGroup / ConfigurationStep are unique (#10626)
omit c7bb74f17f NIFI-15323: Adding operate permissions to the ConnectorEntity. (#10625)
omit 1009473834 NIFI-15259: Added REST API for Connectors
omit aab0c69ac7 NIFI-15258: Addressed PMD findings
omit 314a289823 NIFI-15258: Fixed checkstyle violations/pmd
omit 41266ffa2b NIFI-15258: Fixed vulnerable lz4 dependency
omit 96dce881f0 NIFI-15258: Initial implementation of Connectors
add 6dfddc928a NIFI-15309 Fixed property migration for FetchGridFS (#10620)
add 40b343ff08 NIFI-15297 Support setting configuredForClustering in mock contexts
add edb37a5099 NIFI-15314 Bump Apache Commons Text to 1.15.0, Protobuf to 4.33.2, and others (#10621)
add 46e83d2aea NIFI-15249 Updated version to 2.7.0
add 75b591c8ae NIFI-15249 Merged NIFI-15249-RC4
add a240510810 NIFI-15249 Updated version to 2.8.0-SNAPSHOT
add 5d56f553b0 NIFI-14095 Updated GetFile default Polling Interval to 30 seconds (#10618)
add e4cef0fa09 NIFI-15310 Enabled TestUpdateRecord on Windows (#10622)
add 6e866b85db NIFI-15317 Fixed Script Engine property name migration
add 2e09ce8a30 NIFI-15327 Enabled TestSiteToSiteMetricsReportingTask on Windows.
add 047ad87a3b NIFI-15329 - Fix RenameRecordField to properly handle multiple records (#10629)
add b3b718dd63 NIFI-15319 Decrease memory counter after buffer shutdown in ConsumeKinesis
add 0b9b2b249b NIFI-15320 AwsSecretsManagerParameterProvider should retrieve non-string parameters (#10624)
add c7a16c4cf6 NIFI-15331 Fixed Script Engine property name migration from 2.7.0 (#10634)
add d043d25f56 NIFI-15333 Bump Spring to 6.2.15, Mockito to 5.21.0, Logback to 1.5.22, and others (#10635)
add c11291213a NIFI-15332 Replaced print to console with a log statement when the configured additional classpath in ExecuteGroovyScript does not exist.
add a955ddfcbd NIFI-15340 Fixed property migration for Management Controller Services
add 1e1a1dfd48 NIFI-15337 - Fix optional ComponentStateEntity handling for clear-requests
add d17bc32b4e NIFI-15339 - Bump Netty to 4.2.9.Final, jGit to 7.5.0, Excel Streaming Reader to 5.2.0, and others (#10641)
add 13bb0643f4 NIFI-15299 Improved logic for disabling invalid Controller Services (#10630)
add 2493d3cef9 NIFI-15345 Avoided calling SFTP stat when attributes not configured
add edb81fe660 NIFI-15308 Added support for Zone Offset in Time Field Converters (#10617)
add fe6987d779 NIFI-15347 Fixed property migration for Reporting Tasks (#10648)
add 829f240b83 NIFI-15348 Removed netty-codec-classes-quic exclusion for reactor-netty 1.3.1 compatibility (#10650)
add 4a00f68ba8 NIFI-15350 Add support for Service Principal in StandardAzureCredentialsControllerService (#10649)
add c502c2834f NIFI-15358 Upgraded Maven from 3.9.11 to 3.9.12 (#10658)
add ece597b523 NIFI-8269 - Add support for schema inference in ForkRecord processor when extracting array records
add 09cfea2f54 NIFI-15362 Fixed Time data type parsing in QuerySalesforceObject
add 92018b694a NIFI-15363 Fixed test failure on Windows for ForkRecord (#10662)
add 8bd2734bf6 NIFI-15357 Bump Log4j2 to 2.25.3, Kotlin to 2.3.0, LZ4 to 1.10.2, and others (#10657)
add 2f6a98add5 NIFI-15368 Replaced deprecated class JedisPoolConfig in the Jedis API, with its replacement ConnectionPoolConfig.
add d744ccb671 NIFI-15364 Fixed QUIC NoClassDefFoundError for Azure Parameter Provider (#10663)
add 63789cfa04 NIFI-15354 Added unit tests for property migration in Elasticsearch components (#10656)
add dfd2651a27 NIFI-15338 Switched Jolt bundle to io.github.jolt-community 1.1.0 (#10640)
add 52d1e349ed NIFI-15346 Added unit tests property migration in Box, CDC, Cipher, Confluent, Dropbox Bundles (#10651)
add 672b4af6cd NIFI-15341 Fixed External Service resolution for migrated properties
add bf846bb386 NIFI-15341 Removed unnecessary addition to unresolved Services
add dfcfa9c5cc Merge pull request #10675 from exceptionfactory/NIFI-15341
add 26464ae239 NIFI-15377 Required commit signing for main branch
add 29c70adaca NIFI-15366 Flow Comparison should skip nested separately Version-Controlled PGs (#10668)
add 3c52bed1b4 NIFI-15359 Upgraded S3 Encryption Client from 3.6.0 to 4.0.0 (#10659)
add c06d5e8eca NIFI-15374 Fixed Parameter auto-complete to accept the period character (#10680)
add 2382f61167 NIFI-15380 Bump Logback to 1.5.23, frontend plugin to 2.0.0, elastic to 9.2.3 and others (#10678)
add 056c16a383 NIFI-15244 Adjusting update logic to prevent rollback from duplicating FlowFiles upon journal update failure
add c96c958a57 NIFI-15372: Ensure Driver is Deregistered when DBCP Service is removed.
add 6290c03ecc NIFI-15385 Upgraded Box SDK from 4.16.4 to 5.3.0
add aa8f13ee88 NIFI-15335 Added unit tests for property migration in Airtable, AMQP, Asana, ASN.1 and AWS bundles (#10653)
add 6fd0b05319 NIFI-15334 Added support for deleting a directory in DeleteSFTP (#10684)
add f90a92146d NIFI-15286 Added connection verification to Kafka Connection Services (#10689)
add abf03e1ec0 NIFI-15390 Enabled Tests on Windows for ExecuteGroovyScript and CLI Toolkit (#10694)
add 9d480a090a NIFI-15392 Enabled TestLuceneEventIndex and TestTailFile on Windows (#10695)
add ce173b09cf NIFI-15383 Ensure JDBC Driver deregistered from HikariCPConnectionPool (#10692)
add 419f491974 NIFI-15397 Changed Content SHA not found log from warn to debug for Git Clients (#10701)
add b3c8762d54 NIFI-15396 Added RUN_ONCE to Processor State allowable values (#10702)
add aa8bef4396 NIFI-15400 Added commit signing to PR checklist
add 72dde1e907 NIFI-15400 Adjusted formatting for commit signing checklist item
add badcf3deff NIFI-15401 Removed Asana Components
add b7861f3502 NIFI-15402 Enabled testEcho in TestExecuteProcess and TestExecuteStreamCommand on Windows (#10706)
add a8d21f24fe NIFI-15403 Removed Read Authorization from Current User method
add a316f1648f NIFI-15404 Scheduled Security Maintenance Tasks with Virtual Threads
add 09ace9607b NIFI-15398 Replaced Apache Derby with HSQLDB
add a7d02cb24d NIFI-15387 Disabled testFilterPathPattern on Windows.
add ea8e2f7229 NIFI-15395 Clearing Bulletins with no component returns a 200 (#10700)
add 0018851cb3 NIFI-15305 Fixed Date Time conversion for floating point as string
add e2225798cf NIFI-12598 Allow Starting/Stopping of PG components to be recorded in Flow Configuration History when started/stopped at PG level
add 3c174764e2 NIFI-15394 Included scoped Controller Services when Clearing Bulletins for Process Groups (#10699)
add 5699759c29 NIFI-15351 Show NiFi build information in Prometheus metrics
add 3beed0da3e NIFI-15405 Added Checkstyle indentation and comments indentation rules (#10710)
add 4f6a736f4b NIFI-15378 Removed empty lines from formatted XML in Content Viewer
add e83cb8bf59 NIFI-15407 Reduced logging verbosity in Py4J Integration Tests
add bdf11707a1 NIFI-15389 Updated DBCPConnectionPool to deregister Drivers loaded in classes
add 34c355cc10 NIFI-15221 Upgraded Spring Framework from 6.2.15 to 7.0.2 (#10554)
add 21fa19ac04 NIFI-15406 Add Checkstyle rules for code quality and modern Java best practices (#10712)
add ae51f7e104 NIFI-15411 Removed old Disabled unit tests (#10717)
add 1ed57fcb51 NIFI-15415 Changed instance field in VersionInfoRegistry to match others (#10719)
add 25b364e16f NIFI-15414 Removed DisabledOnOs annotations from AttributeRollingWindowIT (#10718)
add 871a63cea0 NIFI-15387 Re-enabled TestFetchFile, TestListFile and TestPutFile to run on Windows.
add 9d45eecf02 NIFI-15418 Added Checkstyle rules for padding, whitespace, and others (#10723)
add 9998b6d9ce NIFI-15419 Bump gRPC to 1.78.0, Iceberg to 1.10.1, Jsoup to 1.22.1, and others (#10724)
add 04846ca717 NIFI-15426 update Copyright NOTICE to 2026 and removed unused JSch dependency from nifi-registry
add fb7dc98326 NIFI-5941 set default LogAttribute and LogMessage level to TRACE in logback.xml to cover all usage scenarios
add 5e8f7c53b1 NIFI-15420 Added Checkstyle rule for import ordering (#10725)
add 9954d51155 NIFI-15408 Restored integration tests to code coverage workflow (#10714)
add 2aa0ee6998 NIFI-15435 Added check for empty array in MapRecord.toMap() (#10738)
add 52295f7f8e NIFI-15436 Bump Logback to 1.5.24, JUnit to 6.0.2, Kusto to 7.0.5, and others (#10739)
add 91422caaff NIFI-15438 Add JAXB Introspector to ObjectMapper in NiFiRegistryExtensionRegistry (#10741)
add 85b6508ced NIFI-15425 Fixed S2S Remote Process Group Open Receive Connection over HTTP (#10729)
add 2c843bb217 NIFI-15243 Improved KubernetesConfigMapStateProvider Set State Handling (#10619)
add f90656f980 NIFI-15388 Fixed Reference Leak in ReflectionUtils annotation cache (#10691)
add fd41c45291 NIFI-15307 Updated ConsumeKinesis to wait for long initialization in onTrigger (#10664)
add 64e88f6938 NIFI-15444 - Address dependabot alert: qs's arrayLimit bypass in its bracket notation allows DoS via memory exhaustion (#10747)
add 7e4463ae4e NIFI-15260 - Hovering over long flow file names in Queue UI view
add 2ba0e91201 NIFI-15447 Removed teardown method from Component Initializers
add 3a94d26883 NIFI-15431 Fixed error handling in PutSmbFile to allow batch processing to continue
add 98b8010843 NIFI-15437 - UI - Move bulletin copy icon back, next to the severity in bulletin tooltip
add 97c4f42112 NIFI-15456 Bump Snowflake Ingest SDK to 4.4.1-unshaded, Protobuf to 4.33.3, icu4j to 78.2, and others (#10759)
add 372627eea8 NIFI-15455 Use setBytes binding for BINARY SQL Type in PutDatabaseRecord processor
add c2604cb790 NIFI-15452 Changed FlowRegistryClient instances to always require validation (#10757)
add 641cbe4de0 NIFI-15457 Fixed awsTaskId FlowFile attribute in AWS Machine Learning Processors (#10760)
add ae2b734853 NIFI-14969 - Add Content MD5 checksum validation for PutAzureBlobStorage_v12
add 58fc0b18c4 NIFI-15459 - Security: Update Angular to 20.3.16 to patch XSS vulnerability (CVE-2026-22610) (#10762)
add 32cbce652b NIFI-15458 Fixed SnowflakeComputingConnectionPool's Password property
add 5c7bc72fae NIFI-15413 Enabled testArgumentsWithQuotesFromAttributeDynamicProperties on Windows (#10745)
add d4846ae6b3 NIFI-15462 - Bump Protobuf to 4.33.4, Apache Parquet to 1.17.0, Snowflake Ingest SDK to 4.4.2-unshaded, and others (#10763)
add 27985e0a9d NIFI-15450 Added logging message when Asset File sync completes (#10764)
add d8edf57316 NIFI-15405 Restored code inadvertently removed by 3beed0da3e3 (#10765)
add e5f1db1970 NIFI-15224 Added Pre-Processing SQL Properties to PutDatabaseRecord
add 0f53a8482c NIFI-15471 - Address dependabot detected issues in transitive dependency hono
add ab1a336c52 NIFI-15469 Bump Spring to 7.0.3, Reactor Netty to 1.3.2, Elastic to 9.2.4, and others (#10772)
add 0024b1ddbd NIFI-15441 Added udp.sender.port FlowFile Attribute to ListenUDP (#10743)
add d4a1c6267c NIFI-15417 Enabled most TestFileSystemRepository methods on Windows (#10722)
add 0734e6a05a NIFI-15478 - Restart components affected by synchronized assets (#10779)
add 60fb8e0ff7 NIFI-15484 Bump Logback to 1.5.25, Jackson to 2.21.0, PostgreSQL to 42.7.9, and others (#10785)
add 19d8ff0bac NIFI-13987 Add SSL Context Service in GitHubFlowRegistryClient and GitLabFlowRegistryClient (#10788)
add b52727eaaa NIFI-15432 Add system tests to code coverage workflow (#10735)
add ad1b681b3b NIFI-15422 Added unit tests for property migration of Processors in standard-bundle (#10787)
add ab050e0c3c NIFI-15473 Added Execute Code Required Permission annotation to Jolt Processors (#10777)
new 9d87f0947b NIFI-15258: Initial implementation of Connectors
new e04d6729dc NIFI-15258: Fixed vulnerable lz4 dependency
new f80a0b7387 NIFI-15258: Fixed checkstyle violations/pmd
new 673db8e21d NIFI-15258: Addressed PMD findings
new c3393ee65f NIFI-15259: Added REST API for Connectors
new 798594c8ef NIFI-15323: Adding operate permissions to the ConnectorEntity. (#10625)
new 9063b3ebc1 NIFI-15322: Require all property descriptors within a PropertyGroup / ConfigurationStep are unique (#10626)
new 21de437b8a NIFI-15326: Adding support of configuration step documentation. (#10631)
new a470e8b9fa NIFI-15330: Allow GhostConnector to be updated (#10632)
new 36a0ba24de NIFI-15312: Implementation of Parameter Provider based Secrets Manager; updated mock framework to support secrets; validation cleanup (#10638)
new f4c577540e NIFI-15336: Created AuthorizableSecret and implemented ParameterProviderSecretProvider to provide the Parameter Provider as the associated Authorizable (#10644)
new 7f44f3a9d7 NIFI-15258: Enable Custom UI to retrieve connector with flow contexts (#10652)
new c5f53200e4 NIFI-15352: Added ConnectorClient to toolkit-cli so that it can be us… (#10654)
new 3a33552408 NIFI-15343: Adding an endpoint to return available secrets to the con… (#10645)
new dd971a1b27 NIFI-15315 Add support for assets in connectors (#10647)
new e1f0916fe7 NIFI-15361: Allowing configuration step documentation to be returned … (#10667)
new 59b2ad0dca NIFI-15369: Allow ConfigurationStep to depend on another (ConfigurationStep,Property) tuple (#10669)
new e60e994d4e NIFI-15367: Ensure that Connectors' implicit parameter contexts are n… (#10665)
new 5b8af8d409 NIFI-15370: Add Connector ID to process groups and do not register create process groups if they have a connector ID. (#10673)
new 7417597727 NIFI-15356: Adding authorization to the StandardNiFiConnectorWebContext. (#10660)
new 39fe9e1dae NIFI-15353: Adding support for rendering Connector Documentation. (#10683)
new f931e1497e NIFI-15429: Adding an optional query parameter for specifying which p… (#10732)
new dac8e8a013 NIFI-15430: Ensure that if we fail to initialize a Connector, we crea… (#10733)
new ce1b4150a0 NIFI-15427: Added abiliy to drop flowfiles / drain flowfiles from a C… (#10730)
new fae768c537 NIFI-15433: If connector validation throws an Exception keep trying u… (#10736)
new acd54791a5 NIFI-15434: Ensure that we start or stop connectors on flow sync (#10737)
new e07c90b7a6 NIFI-15445: Removed 'disabled' state from Connectors; also fixed bug … (#10749)
new 8332e6db55 NIFI-15440: Implementation of ConnectorActions (#10748)
new 4c98a8842f NIFI-15439: Ensure that Process Groups are accessible from the approp… (#10742)
new 8414bc7fcd NIFI-15468: Ensure that we properly initialize ConnectorNode even if a Ghost Connector is created (#10775)
new 58735ea30e NIFI-15446: When invoking ConnectorMethod, make sure to serialize/des… (#10750)
new 1be2089dae NIFI-15376: Adding dependencies to the configuration step dto. (#10674)
new b7420f7535 NIFI-15453: Adding an endpoint to return all controller services for … (#10770)
new b0481062fa NIFI-15472: Added addAsset methods to ConnectorTestRunner (#10776)
new cda2304051 NIFI-15482: MockConnectorAssetManager requires SPI file to start (#10783)
new 7a288a57bf NIFI-15451: Added ability for Connectors to retrieve bundles availabl… (#10756)
new 6af93e06f5 NIFI-15481: Updated ConnectorActions to reflect that starting is not … (#10782)
new 109faf50a0 NIFI-15258: Fixes necessary after rebasing against main
new afaa1be9b2 NIFI-15258: Addressed checkstyle violations
new 135d3f0d70 NIFI-15461: Added ability to initiate drainage of Connector's FlowFiles and calce; added tests to verify; some bug fixes (#10767)
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (b515661ba4)
\
N -- N -- N refs/heads/NIFI-15258 (135d3f0d70)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 40 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.asf.yaml | 6 +-
.github/PULL_REQUEST_TEMPLATE.md | 1 +
.github/scripts/collect-system-tests-coverage.sh | 95 +
.github/workflows/code-coverage.yml | 146 +-
.gitignore | 1 +
.mvn/wrapper/maven-wrapper.properties | 4 +-
NOTICE | 2 +-
c2/c2-client-bundle/c2-client-api/pom.xml | 4 +-
.../org/apache/nifi/c2/client/api/C2Client.java | 7 +-
c2/c2-client-bundle/c2-client-base/pom.xml | 4 +-
.../org/apache/nifi/c2/client/C2ClientConfig.java | 9 +-
.../nifi/c2/serializer/C2JacksonSerializer.java | 9 +-
.../apache/nifi/c2/serializer/C2Serializer.java | 1 +
c2/c2-client-bundle/c2-client-http/pom.xml | 10 +-
.../apache/nifi/c2/client/http/C2HttpClient.java | 25 +-
.../nifi/c2/client/http/C2RequestCompression.java | 7 +-
.../nifi/c2/client/http/OkHttpClientProvider.java | 13 +-
.../c2/client/http/url/C2UrlProviderFactory.java | 4 +-
.../c2/client/http/url/LegacyC2UrlProvider.java | 9 +-
.../client/http/url/ProxyAwareC2UrlProvider.java | 11 +-
.../nifi/c2/client/http/C2HttpClientTest.java | 2 +-
.../c2/client/http/C2RequestCompressionTest.java | 21 +-
.../client/http/url/C2UrlProviderFactoryTest.java | 10 +-
.../client/http/url/LegacyC2UrlProviderTest.java | 11 +-
.../client/http/url/ProxyAwareC2ProviderTest.java | 13 +-
c2/c2-client-bundle/c2-client-service/pom.xml | 10 +-
.../nifi/c2/client/service/C2HeartbeatFactory.java | 49 +-
.../nifi/c2/client/service/C2HeartbeatManager.java | 20 +-
.../nifi/c2/client/service/C2OperationManager.java | 25 +-
.../nifi/c2/client/service/FlowIdHolder.java | 9 +-
.../c2/client/service/ManifestHashProvider.java | 10 +-
.../client/service/model/RuntimeInfoWrapper.java | 5 +-
.../service/operation/C2OperationHandler.java | 13 +-
.../operation/C2OperationHandlerProvider.java | 11 +-
.../operation/C2OperationRestartHandler.java | 3 +-
.../DescribeManifestOperationHandler.java | 19 +-
.../client/service/operation/OperationQueue.java | 5 +-
.../operation/StartFlowOperationHandler.java | 12 +-
.../operation/StopFlowOperationHandler.java | 12 +-
.../operation/SupportedOperationsProvider.java | 7 +-
.../operation/SyncResourceOperationHandler.java | 21 +-
.../service/operation/SyncResourceStrategy.java | 7 +-
.../operation/TransferDebugOperationHandler.java | 51 +-
.../operation/UpdateAssetOperationHandler.java | 27 +-
.../UpdateConfigurationOperationHandler.java | 29 +-
.../UpdatePropertiesOperationHandler.java | 21 +-
.../nifi/minifi/validator/ValidationException.java | 11 +-
.../c2/client/service/C2HeartbeatFactoryTest.java | 37 +-
.../c2/client/service/C2HeartbeatManagerTest.java | 28 +-
.../c2/client/service/C2OperationManagerTest.java | 43 +-
.../client/service/ManifestHashProviderTest.java | 15 +-
.../operation/C2OperationHandlerProviderTest.java | 23 +-
.../DescribeManifestOperationHandlerTest.java | 8 +-
.../EmptyOperandPropertiesProviderTest.java | 7 +-
.../operation/StartFlowOperationHandlerTest.java | 5 +-
.../operation/StopFlowOperationHandlerTest.java | 5 +-
.../operation/SupportedOperationsProviderTest.java | 18 +-
.../SyncResourceOperationHandlerTest.java | 43 +-
.../TransferDebugOperationHandlerTest.java | 65 +-
.../operation/UpdateAssetOperationHandlerTest.java | 39 +-
.../UpdateConfigurationOperationHandlerTest.java | 31 +-
.../UpdatePropertiesOperationHandlerTest.java | 23 +-
c2/c2-client-bundle/pom.xml | 2 +-
c2/c2-protocol/c2-protocol-api/pom.xml | 4 +-
.../apache/nifi/c2/protocol/api/AgentManifest.java | 8 +-
.../apache/nifi/c2/protocol/api/C2Operation.java | 2 +-
.../nifi/c2/protocol/api/C2OperationAck.java | 1 +
.../apache/nifi/c2/protocol/api/DeviceInfo.java | 8 +-
.../apache/nifi/c2/protocol/api/FailureCause.java | 11 +-
.../apache/nifi/c2/protocol/api/OperationType.java | 10 +-
.../nifi/c2/protocol/api/ProcessorBulletin.java | 1 +
.../apache/nifi/c2/protocol/api/ResourceInfo.java | 1 +
.../apache/nifi/c2/protocol/api/ResourceItem.java | 1 +
.../nifi/c2/protocol/api/ResourcesGlobalHash.java | 1 +
c2/c2-protocol/c2-protocol-component-api/pom.xml | 2 +-
.../component/api/ProcessorConfiguration.java | 1 -
.../c2/protocol/component/api/Restriction.java | 8 +-
c2/c2-protocol/pom.xml | 2 +-
c2/pom.xml | 2 +-
checkstyle.xml | 43 +
minifi/minifi-assembly/NOTICE | 2 +-
minifi/minifi-assembly/pom.xml | 52 +-
minifi/minifi-bom/pom.xml | 8 +-
minifi/minifi-bootstrap/pom.xml | 20 +-
.../apache/nifi/minifi/bootstrap/RunMiNiFi.java | 23 +-
.../nifi/minifi/bootstrap/SensitiveProperty.java | 6 +-
.../apache/nifi/minifi/bootstrap/ShutdownHook.java | 1 -
.../nifi/minifi/bootstrap/WindowsService.java | 3 +-
.../bootstrap/command/CommandRunnerFactory.java | 13 +-
.../bootstrap/command/CompositeCommandRunner.java | 4 +-
.../nifi/minifi/bootstrap/command/DumpRunner.java | 15 +-
.../nifi/minifi/bootstrap/command/EnvRunner.java | 9 +-
.../minifi/bootstrap/command/FlowStatusRunner.java | 4 +-
.../nifi/minifi/bootstrap/command/StartRunner.java | 49 +-
.../minifi/bootstrap/command/StatusRunner.java | 8 +-
.../nifi/minifi/bootstrap/command/StopRunner.java | 21 +-
.../ConfigurationChangeCoordinator.java | 21 +-
.../ConfigurationChangeException.java | 1 -
.../differentiators/Differentiator.java | 5 +-
.../differentiators/WholeConfigDifferentiator.java | 11 +-
.../ingestors/AbstractPullChangeIngestor.java | 9 +-
.../ingestors/FileChangeIngestor.java | 43 +-
.../ingestors/PullHttpChangeIngestor.java | 58 +-
.../ingestors/RestChangeIngestor.java | 36 +-
.../ingestors/interfaces/ChangeIngestor.java | 3 +-
.../exception/InvalidCommandException.java | 1 -
.../exception/InvalidConfigurationException.java | 1 -
.../minifi/bootstrap/service/BootstrapCodec.java | 13 +-
.../bootstrap/service/BootstrapFileProvider.java | 19 +-
.../bootstrap/service/CurrentPortProvider.java | 6 +-
.../service/GracefulShutdownParameterProvider.java | 3 +-
.../bootstrap/service/MiNiFiCommandSender.java | 7 +-
.../service/MiNiFiConfigurationChangeListener.java | 33 +-
.../service/MiNiFiExecCommandProvider.java | 15 +-
.../minifi/bootstrap/service/MiNiFiListener.java | 11 +-
.../service/MiNiFiPropertiesGenerator.java | 49 +-
.../bootstrap/service/MiNiFiStatusProvider.java | 4 +-
.../bootstrap/service/MiNiFiStdLogHandler.java | 11 +-
.../service/PeriodicStatusReporterManager.java | 17 +-
.../minifi/bootstrap/service/ReloadService.java | 11 +-
.../bootstrap/service/UpdatePropertiesService.java | 15 +-
.../bootstrap/status/PeriodicStatusReporter.java | 5 +-
.../bootstrap/status/reporters/StatusLogger.java | 11 +-
.../minifi/bootstrap/util/OrderedProperties.java | 3 +-
.../minifi/bootstrap/util/UnixProcessUtils.java | 7 +-
.../nifi/minifi/bootstrap/ShutdownHookTest.java | 10 +-
.../command/CommandRunnerFactoryTest.java | 31 +-
.../command/CompositeCommandRunnerTest.java | 17 +-
.../minifi/bootstrap/command/DumpRunnerTest.java | 27 +-
.../minifi/bootstrap/command/EnvRunnerTest.java | 23 +-
.../bootstrap/command/FlowStatusRunnerTest.java | 17 +-
.../minifi/bootstrap/command/StatusRunnerTest.java | 14 +-
.../minifi/bootstrap/command/StopRunnerTest.java | 33 +-
.../WholeConfigDifferentiatorTest.java | 19 +-
.../ingestors/FileChangeIngestorTest.java | 39 +-
.../ingestors/RestChangeIngestorCommonTest.java | 7 +-
.../ingestors/RestChangeIngestorTest.java | 23 +-
.../bootstrap/service/BootstrapCodecTest.java | 33 +-
.../bootstrap/service/CurrentPortProviderTest.java | 16 +-
.../GracefulShutdownParameterProviderTest.java | 23 +-
.../service/MiNiFiPropertiesGeneratorTest.java | 43 +-
.../status/reporters/StatusLoggerTest.java | 27 +-
minifi/minifi-commons/minifi-commons-api/pom.xml | 4 +-
.../nifi/minifi/commons/api/MiNiFiProperties.java | 16 +-
.../minifi-commons-framework/pom.xml | 14 +-
.../commons/service/StandardFlowEnrichService.java | 49 +-
...dFlowPropertyAssetReferenceResolverService.java | 2 +-
.../service/StandardFlowPropertyEncryptor.java | 29 +-
.../commons/service/StandardFlowSerDeService.java | 9 +-
.../nifi/minifi/commons/util/FlowUpdateUtils.java | 19 +-
.../service/StandardFlowEnrichServiceTest.java | 35 +-
...wPropertyAssetReferenceResolverServiceTest.java | 2 +-
.../service/StandardFlowPropertyEncryptorTest.java | 44 +-
minifi/minifi-commons/minifi-commons-utils/pom.xml | 2 +-
.../minifi/commons/utils/PropertyUtilTest.java | 13 +-
.../minifi-flow-status-report/pom.xml | 2 +-
.../minifi/commons/status/FlowStatusReport.java | 35 +-
.../commons/status/common/BulletinStatus.java | 12 +-
.../commons/status/common/ValidationError.java | 16 +-
.../status/connection/ConnectionHealth.java | 12 +-
.../commons/status/connection/ConnectionStats.java | 20 +-
.../status/connection/ConnectionStatusBean.java | 16 +-
.../controllerservice/ControllerServiceHealth.java | 16 +-
.../controllerservice/ControllerServiceStatus.java | 16 +-
.../commons/status/instance/InstanceHealth.java | 20 +-
.../commons/status/instance/InstanceStats.java | 36 +-
.../commons/status/instance/InstanceStatus.java | 16 +-
.../commons/status/processor/ProcessorHealth.java | 16 +-
.../commons/status/processor/ProcessorStats.java | 32 +-
.../status/processor/ProcessorStatusBean.java | 20 +-
.../status/reportingTask/ReportingTaskHealth.java | 20 +-
.../status/reportingTask/ReportingTaskStatus.java | 16 +-
.../nifi/minifi/commons/status/rpg/PortStatus.java | 16 +-
.../status/rpg/RemoteProcessGroupHealth.java | 20 +-
.../status/rpg/RemoteProcessGroupStats.java | 16 +-
.../status/rpg/RemoteProcessGroupStatusBean.java | 28 +-
.../status/system/ContentRepositoryUsage.java | 24 +-
.../status/system/FlowfileRepositoryUsage.java | 20 +-
.../status/system/GarbageCollectionStatus.java | 16 +-
.../minifi/commons/status/system/HeapStatus.java | 44 +-
.../status/system/SystemDiagnosticsStatus.java | 24 +-
.../status/system/SystemProcessorStats.java | 12 +-
minifi/minifi-commons/pom.xml | 2 +-
minifi/minifi-docker/pom.xml | 2 +-
minifi/minifi-docs/NOTICE | 2 +-
minifi/minifi-docs/pom.xml | 2 +-
minifi/minifi-integration-tests/pom.xml | 6 +-
.../standalone/test/StandaloneJsonTest.java | 14 +-
.../src/test/resources/conf/nifi.properties | 2 +-
.../minifi-framework-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../minifi-framework/minifi-framework-api/pom.xml | 6 +-
.../java/org/apache/nifi/minifi/MiNiFiServer.java | 1 -
.../nifi/minifi/status/StatusRequestException.java | 1 -
.../minifi-framework/minifi-framework-core/pom.xml | 38 +-
.../c2/BootstrapC2OperationRestartHandler.java | 29 +-
.../apache/nifi/minifi/c2/C2NifiClientService.java | 114 +-
.../nifi/minifi/c2/FileBasedOperationQueueDAO.java | 9 +-
.../c2/command/AgentPropertyValidationContext.java | 165 +-
.../c2/command/DefaultFlowStateStrategy.java | 1 +
.../c2/command/DefaultProcessorStateStrategy.java | 4 +-
.../DefaultUpdateConfigurationStrategy.java | 47 +-
.../minifi/c2/command/PropertiesPersister.java | 15 +-
.../c2/command/TransferDebugCommandHelper.java | 13 +-
.../c2/command/UpdateAssetCommandHelper.java | 5 +-
.../command/UpdatePropertiesPropertyProvider.java | 11 +-
.../syncresource/DefaultSyncResourceStrategy.java | 35 +-
.../syncresource/FileResourceRepository.java | 25 +-
.../command/syncresource/ResourceRepository.java | 5 +-
.../syncresource/SyncResourcePropertyProvider.java | 1 +
.../nifi/minifi/validator/FlowValidator.java | 47 +-
.../c2/BootstrapC2OperationRestartHandlerTest.java | 27 +-
.../minifi/c2/FileBasedOperationQueueDAOTest.java | 35 +-
.../c2/command/DefaultFlowStateStrategyTest.java | 2 +-
.../DefaultUpdateConfigurationStrategyTest.java | 53 +-
.../minifi/c2/command/PropertiesPersisterTest.java | 31 +-
.../c2/command/TransferDebugCommandHelperTest.java | 7 +-
.../c2/command/UpdateAssetCommandHelperTest.java | 19 +-
.../UpdatePropertiesPropertyProviderTest.java | 23 +-
.../DefaultSyncResourceStrategyTest.java | 58 +-
.../syncresource/FileResourceRepositoryTest.java | 37 +-
.../minifi-framework/minifi-nar-unloader/pom.xml | 14 +-
.../nifi/minifi/nar/NarAutoUnloadService.java | 1 +
.../apache/nifi/minifi/nar/NarAutoUnloader.java | 4 +-
.../nifi/minifi/nar/NarAutoUnloaderTask.java | 9 +-
.../minifi/nar/NarAutoUnloaderTaskFactory.java | 11 +-
.../nifi/minifi/nar/NarAutoUnloadServiceTest.java | 11 +-
.../minifi-properties-loader/pom.xml | 10 +-
.../minifi/properties/BootstrapProperties.java | 5 +-
.../properties/DuplicateDetectingProperties.java | 1 -
.../minifi/properties/MiNiFiPropertiesLoader.java | 6 +-
.../properties/MultiSourceMinifiProperties.java | 5 +-
.../nifi/minifi/properties/PropertiesLoader.java | 5 +-
.../minifi-framework/minifi-resources/NOTICE | 2 +-
.../minifi-framework/minifi-resources/pom.xml | 2 +-
.../minifi-framework/minifi-runtime/pom.xml | 18 +-
.../main/java/org/apache/nifi/minifi/MiNiFi.java | 29 +-
.../nifi/minifi/bootstrap/BootstrapListener.java | 21 +-
.../minifi/util/BootstrapClassLoaderUtils.java | 5 +-
.../minifi-framework/minifi-server/pom.xml | 18 +-
.../apache/nifi/minifi/StandardMiNiFiServer.java | 22 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../minifi-framework/pom.xml | 2 +-
.../minifi-server-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../minifi-framework-bundle/pom.xml | 16 +-
.../minifi-provenance-repositories/pom.xml | 6 +-
.../nifi/provenance/NoOpProvenanceRepository.java | 296 +-
.../minifi-provenance-repository-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../minifi-provenance-repository-bundle/pom.xml | 2 +-
.../minifi-nar-bundles/minifi-standard-nar/pom.xml | 12 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../minifi-update-attribute-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
minifi/minifi-nar-bundles/pom.xml | 2 +-
.../minifi-toolkit/minifi-toolkit-assembly/NOTICE | 4 +-
.../minifi-toolkit/minifi-toolkit-assembly/pom.xml | 4 +-
.../minifi-toolkit-configuration/pom.xml | 10 +-
.../minifi/toolkit/configuration/ConfigMain.java | 8 +-
.../configuration/ConfigTransformException.java | 2 +-
.../json/ComponentPropertyProvider.java | 19 +-
...ConfigSchemaToVersionedDataFlowTransformer.java | 79 +-
.../json/TransformNifiCommandFactory.java | 11 +-
.../json/TransformYamlCommandFactory.java | 27 +-
.../minifi-toolkit/minifi-toolkit-schema/pom.xml | 4 +-
.../nifi/minifi/toolkit/schema/FunnelSchema.java | 2 +-
.../minifi/toolkit/schema/ReportingSchema.java | 2 +-
.../toolkit/schema/SecurityPropertiesSchema.java | 6 +-
.../minifi/toolkit/schema/common/BaseSchema.java | 3 +-
.../toolkit/schema/common/BaseSchemaWithId.java | 2 +-
.../toolkit/schema/serialization/SchemaLoader.java | 2 +-
.../minifi/toolkit/schema/v2/ConfigSchemaV2.java | 32 +-
.../toolkit/schema/v2/CorePropertiesSchemaV2.java | 9 +-
minifi/minifi-toolkit/pom.xml | 2 +-
minifi/pom.xml | 2 +-
nifi-assembly/LICENSE | 25 -
nifi-assembly/NOTICE | 2 +-
nifi-assembly/pom.xml | 316 +-
nifi-bom/pom.xml | 20 +-
nifi-bootstrap/pom.xml | 4 +-
.../command/StandardBootstrapCommandProvider.java | 2 +-
.../StandardConfigurationProvider.java | 4 +-
.../SecurityApplicationPropertyHandler.java | 2 +-
nifi-code-coverage/pom.xml | 632 +-
nifi-commons/nifi-build/pom.xml | 2 +-
nifi-commons/nifi-calcite-utils/pom.xml | 10 +-
nifi-commons/nifi-connector-utils/pom.xml | 2 +-
nifi-commons/nifi-content-viewer-utils/pom.xml | 2 +-
nifi-commons/nifi-custom-ui-utils/pom.xml | 2 +-
nifi-commons/nifi-data-provenance-utils/pom.xml | 2 +-
.../nifi/provenance/AsyncLineageSubmission.java | 6 +-
.../nifi/provenance/AsyncQuerySubmission.java | 6 +-
.../nifi/provenance/SearchableFieldParser.java | 4 +-
.../nifi/provenance/StandardQueryResult.java | 10 +-
.../apache/nifi/provenance/lineage/EventNode.java | 4 +-
nifi-commons/nifi-deprecation-log/pom.xml | 2 +-
nifi-commons/nifi-expression-language/pom.xml | 8 +-
.../expression/language/AttributesAndState.java | 1 -
.../expression/language/InvalidPreparedQuery.java | 1 -
.../attribute/expression/language/ValueLookup.java | 2 +-
.../expression/language/VariableImpact.java | 4 +-
.../language/compile/ExpressionCompiler.java | 52 +-
.../language/evaluation/InstantQueryResult.java | 2 +-
.../functions/CharSequenceTranslatorEvaluator.java | 20 +-
.../evaluation/functions/HashEvaluator.java | 8 +-
.../evaluation/functions/HostnameEvaluator.java | 1 -
.../functions/JsonPathBaseEvaluator.java | 19 +-
.../functions/JsonPathDeleteEvaluator.java | 7 +-
.../evaluation/functions/JsonPathEvaluator.java | 5 +-
.../evaluation/functions/JsonPathSetEvaluator.java | 1 -
.../functions/JsonPathUpdateEvaluator.java | 1 -
.../evaluation/functions/PadLeftEvaluator.java | 1 -
.../evaluation/functions/PaddingEvaluator.java | 2 +-
.../evaluation/functions/Uuid3Evaluator.java | 2 +-
.../evaluation/functions/Uuid5Evaluator.java | 14 +-
.../literals/DecimalLiteralEvaluator.java | 1 -
.../evaluation/selection/ParameterEvaluator.java | 2 +-
.../exception/IllegalAttributeException.java | 1 -
.../attribute/expression/language/TestQuery.java | 21 +-
nifi-commons/nifi-flow-encryptor/pom.xml | 4 +-
.../encryptor/command/FlowEncryptorCommand.java | 11 +-
.../nifi/flow/encryptor/JsonFlowEncryptorTest.java | 15 +-
nifi-commons/nifi-flowfile-packager/pom.xml | 2 +-
.../org/apache/nifi/util/FlowFilePackagerV1.java | 8 +-
.../org/apache/nifi/util/FlowFileUnpackagerV1.java | 9 +-
.../org/apache/nifi/util/FlowFileUnpackagerV2.java | 2 +-
.../org/apache/nifi/util/FlowFileUnpackagerV3.java | 2 +-
nifi-commons/nifi-hashicorp-vault-api/pom.xml | 2 +-
nifi-commons/nifi-hashicorp-vault/pom.xml | 20 +-
.../hashicorp/config/HashiCorpVaultProperty.java | 1 -
nifi-commons/nifi-hl7-query-language/pom.xml | 2 +-
.../java/org/apache/nifi/hl7/hapi/EmptyField.java | 6 +-
.../java/org/apache/nifi/hl7/hapi/HapiField.java | 13 +-
.../java/org/apache/nifi/hl7/hapi/HapiMessage.java | 17 +-
.../java/org/apache/nifi/hl7/hapi/HapiSegment.java | 13 +-
.../org/apache/nifi/hl7/hapi/SingleValueField.java | 6 +-
.../java/org/apache/nifi/hl7/io/HL7Reader.java | 4 +-
.../nifi/hl7/io/exception/InvalidHL7Exception.java | 1 -
.../java/org/apache/nifi/hl7/query/HL7Query.java | 40 +-
.../comparison/AbstractComparisonEvaluator.java | 10 +-
.../comparison/AbstractNumericComparison.java | 4 +-
.../evaluator/comparison/IsNullEvaluator.java | 6 +-
.../query/evaluator/comparison/NotEvaluator.java | 4 +-
.../evaluator/comparison/NotNullEvaluator.java | 6 +-
.../evaluator/literal/IntegerLiteralEvaluator.java | 4 +-
.../evaluator/literal/StringLiteralEvaluator.java | 4 +-
.../hl7/query/evaluator/logic/AndEvaluator.java | 4 +-
.../hl7/query/evaluator/logic/OrEvaluator.java | 4 +-
.../message/DeclaredReferenceEvaluator.java | 4 +-
.../hl7/query/evaluator/message/DotEvaluator.java | 10 +-
.../query/evaluator/message/FieldEvaluator.java | 10 +-
.../query/evaluator/message/MessageEvaluator.java | 4 +-
.../query/evaluator/message/SegmentEvaluator.java | 8 +-
.../query/exception/HL7QueryParsingException.java | 1 -
.../apache/nifi/hl7/query/result/MissedResult.java | 6 +-
.../nifi/hl7/query/result/StandardQueryResult.java | 8 +-
.../nifi/hl7/query/result/StandardResultHit.java | 4 +-
nifi-commons/nifi-jetty-configuration/pom.xml | 2 +-
.../connector/StandardServerConnectorFactory.java | 2 +-
.../StandardServerConnectorFactoryTest.java | 3 +-
nifi-commons/nifi-json-schema-api/pom.xml | 2 +-
nifi-commons/nifi-json-schema-shared/pom.xml | 4 +-
nifi-commons/nifi-json-utils/pom.xml | 2 +-
nifi-commons/nifi-kubernetes-client/pom.xml | 2 +-
nifi-commons/nifi-metrics/pom.xml | 2 +-
.../org/apache/nifi/metrics/jvm/JmxJvmMetrics.java | 2 +-
.../org/apache/nifi/metrics/jvm/JvmMetrics.java | 9 +-
nifi-commons/nifi-parameter/pom.xml | 2 +-
...TestExpressionLanguageAwareParameterParser.java | 1 -
.../nifi-per-process-group-logging/pom.xml | 2 +-
nifi-commons/nifi-properties/pom.xml | 4 +-
.../java/org/apache/nifi/util/NiFiProperties.java | 2 +-
.../resources/NiFiProperties/conf/nifi.properties | 2 +-
nifi-commons/nifi-property-encryptor/pom.xml | 4 +-
.../nifi/encrypt/CipherPropertyEncryptor.java | 6 +-
.../nifi/encrypt/KeyedCipherPropertyEncryptor.java | 6 +-
.../nifi/encrypt/PropertyEncryptorBuilder.java | 2 +-
.../encrypt/StandardPropertySecretKeyProvider.java | 2 +-
.../encrypt/KeyedCipherPropertyEncryptorTest.java | 4 +-
nifi-commons/nifi-property-utils/pom.xml | 2 +-
.../AbstractBootstrapPropertiesLoader.java | 2 +-
.../nifi/properties/BootstrapProperties.java | 2 +-
.../java/org/apache/nifi/util/StringUtilsTest.java | 216 +-
nifi-commons/nifi-record-path/pom.xml | 8 +-
.../nifi/record/path/RecordFieldRemover.java | 9 +-
.../org/apache/nifi/record/path/RecordPath.java | 6 +-
.../nifi/record/path/filter/ContainsRegex.java | 6 +-
.../nifi/record/path/filter/MatchesRegex.java | 6 +-
.../path/filter/NumericBinaryOperatorFilter.java | 2 +-
.../record/path/filter/StringComparisonFilter.java | 4 +-
.../apache/nifi/record/path/functions/Concat.java | 6 +-
.../nifi/record/path/functions/FieldName.java | 4 +-
.../apache/nifi/record/path/functions/MapOf.java | 2 +-
.../apache/nifi/record/path/functions/PadLeft.java | 4 +-
.../nifi/record/path/functions/PadRight.java | 4 +-
.../apache/nifi/record/path/functions/Padding.java | 4 +-
.../nifi/record/path/functions/RecordOf.java | 2 +-
.../apache/nifi/record/path/functions/Replace.java | 4 +-
.../nifi/record/path/functions/ReplaceNull.java | 6 +-
.../nifi/record/path/functions/ReplaceRegex.java | 8 +-
.../nifi/record/path/functions/Substring.java | 8 +-
.../nifi/record/path/functions/SubstringAfter.java | 4 +-
.../record/path/functions/SubstringAfterLast.java | 4 +-
.../record/path/functions/SubstringBefore.java | 4 +-
.../record/path/functions/SubstringBeforeLast.java | 4 +-
.../nifi/record/path/paths/ArrayIndexPath.java | 4 +-
.../nifi/record/path/paths/CurrentFieldPath.java | 4 +-
.../record/path/paths/DescendantFieldPath.java | 11 +-
.../nifi/record/path/paths/LiteralValuePath.java | 4 +-
.../record/path/paths/MultiArrayIndexPath.java | 8 +-
.../nifi/record/path/paths/MultiMapKeyPath.java | 8 +-
.../apache/nifi/record/path/paths/ParentPath.java | 3 +-
.../nifi/record/path/paths/PredicatePath.java | 4 +-
.../nifi/record/path/paths/RecordPathCompiler.java | 4 +-
.../nifi/record/path/paths/RecordPathSegment.java | 6 +-
.../apache/nifi/record/path/paths/RootPath.java | 4 +-
.../nifi/record/path/paths/SingularMapKeyPath.java | 6 +-
.../nifi/record/path/paths/WildcardChildPath.java | 6 +-
.../record/path/paths/WildcardDescendantPath.java | 11 +-
.../nifi/record/path/paths/WildcardIndexPath.java | 8 +-
.../path/util/FieldValueLogicalPathBuilder.java | 8 +-
.../nifi/record/path/util/FieldValueWalker.java | 4 +-
.../org/apache/nifi/record/path/util/Filters.java | 9 +-
.../nifi/record/path/util/RecordPathUtils.java | 4 +-
.../apache/nifi/record/path/TestRecordPath.java | 6 +-
.../util/TestFieldValueLogicalPathBuilder.java | 2 +-
.../record/path/util/TestFieldValueWalker.java | 2 +-
nifi-commons/nifi-record/pom.xml | 2 +-
.../serialization/AbstractRecordSetWriter.java | 6 +-
.../apache/nifi/serialization/RecordSetWriter.java | 4 +-
.../apache/nifi/serialization/RecordWriter.java | 4 +-
.../nifi/serialization/record/MapRecord.java | 40 +
.../nifi/serialization/record/RawRecordWriter.java | 4 +-
.../apache/nifi/serialization/record/Record.java | 4 +-
.../nifi/serialization/record/RecordFieldType.java | 2 +-
.../serialization/record/ResultSetRecordSet.java | 6 +-
.../serialization/record/SchemaIdentifier.java | 2 +-
.../field/ObjectLocalDateTimeFieldConverter.java | 82 +-
.../field/ObjectLocalTimeFieldConverter.java | 32 +-
.../serialization/record/type/ArrayDataType.java | 12 +-
.../serialization/record/type/EnumDataType.java | 12 +-
.../serialization/record/type/MapDataType.java | 12 +-
.../serialization/record/util/DataTypeSet.java | 2 +-
.../record/ResultSetRecordSetTest.java | 6 +-
.../serialization/record/TestDataTypeUtils.java | 10 +-
.../nifi/serialization/record/TestMapRecord.java | 44 +-
.../record/field/DateTimeTestUtil.java | 23 +-
.../field/ObjectLocalTimeFieldConverterTest.java | 56 +
.../record/field/ObjectTimeFieldConverterTest.java | 28 +
.../TestObjectLocalDateTimeFieldConverter.java | 21 +-
nifi-commons/nifi-schema-utils/pom.xml | 2 +-
.../nifi/repository/schema/MapRecordField.java | 4 +-
.../nifi/repository/schema/UnionRecordField.java | 4 +-
nifi-commons/nifi-security-cert-builder/pom.xml | 2 +-
.../cert/builder/StandardCertificateBuilder.java | 2 +-
.../builder/StandardCertificateBuilderTest.java | 2 +-
nifi-commons/nifi-security-cert/pom.xml | 2 +-
.../nifi/security/cert/PeerIdentityProvider.java | 2 +-
.../cert/StandardPeerIdentityProvider.java | 2 +-
.../security/cert/StandardPrincipalFormatter.java | 2 +-
.../cert/StandardPeerIdentityProviderTest.java | 2 +-
.../cert/StandardPrincipalFormatterTest.java | 2 +-
nifi-commons/nifi-security-crypto-key/pom.xml | 2 +-
nifi-commons/nifi-security-identity/pom.xml | 2 +-
nifi-commons/nifi-security-kerberos-api/pom.xml | 2 +-
.../org/apache/nifi/security/krb/KerberosUser.java | 2 +-
nifi-commons/nifi-security-kerberos/pom.xml | 4 +-
.../nifi/security/krb/AbstractKerberosUser.java | 14 +-
.../nifi/security/krb/KeytabConfiguration.java | 4 +-
.../nifi/security/krb/PasswordConfiguration.java | 4 +-
.../security/krb/TicketCacheConfiguration.java | 4 +-
.../krb/UsernamePasswordCallbackHandler.java | 2 +-
.../apache/nifi/security/krb/KerberosUserIT.java | 8 +-
nifi-commons/nifi-security-proxied-entity/pom.xml | 2 +-
nifi-commons/nifi-security-ssl/pom.xml | 2 +-
.../security/ssl/StandardKeyManagerBuilder.java | 6 +-
.../ssl/StandardKeyManagerFactoryBuilder.java | 2 +-
.../security/ssl/StandardSslContextBuilder.java | 10 +-
.../security/ssl/StandardTrustManagerBuilder.java | 6 +-
.../ssl/StandardTrustManagerFactoryBuilder.java | 2 +-
.../ssl/StandardX509ExtendedKeyManager.java | 4 +-
.../ssl/StandardX509ExtendedTrustManager.java | 4 +-
.../ssl/StandardSslContextBuilderTest.java | 4 +-
.../ssl/StandardX509ExtendedKeyManagerTest.java | 3 +-
.../ssl/StandardX509ExtendedTrustManagerTest.java | 4 +-
nifi-commons/nifi-security-utils-api/pom.xml | 2 +-
.../org/apache/nifi/security/util/TlsPlatform.java | 9 +-
nifi-commons/nifi-single-user-utils/pom.xml | 4 +-
.../writer/StandardLoginCredentialsWriter.java | 18 +-
nifi-commons/nifi-site-to-site-client/pom.xml | 12 +-
.../apache/nifi/remote/AbstractTransaction.java | 4 +-
.../src/main/java/org/apache/nifi/remote/Peer.java | 4 +-
.../nifi/remote/RemoteResourceInitiator.java | 8 +-
.../java/org/apache/nifi/remote/Transaction.java | 4 +-
.../apache/nifi/remote/TransactionCompletion.java | 4 +-
.../remote/client/AbstractSiteToSiteClient.java | 2 +-
.../apache/nifi/remote/client/PeerSelector.java | 25 +-
.../nifi/remote/client/PeerStatusProvider.java | 5 +-
.../nifi/remote/client/SiteInfoProvider.java | 15 +-
.../nifi/remote/client/SiteToSiteClient.java | 27 +-
.../nifi/remote/client/SiteToSiteClientConfig.java | 11 +-
.../apache/nifi/remote/client/http/HttpClient.java | 25 +-
.../client/socket/EndpointConnectionPool.java | 53 +-
.../socket/StandardSocketPeerIdentityProvider.java | 8 +-
.../remote/cluster/ClusterNodeInformation.java | 12 +-
.../apache/nifi/remote/codec/FlowFileCodec.java | 10 +-
.../remote/io/http/HttpCommunicationsSession.java | 1 -
.../io/http/HttpServerCommunicationsSession.java | 1 -
.../io/socket/SocketCommunicationsSession.java | 1 -
.../org/apache/nifi/remote/protocol/Response.java | 4 +-
.../protocol/http/HttpClientTransaction.java | 14 +-
.../protocol/socket/SocketClientProtocol.java | 19 +-
.../apache/nifi/remote/util/PeerStatusCache.java | 3 +-
.../nifi/remote/util/SiteToSiteRestApiClient.java | 53 +-
.../nifi/remote/util/StandardDataPacket.java | 6 +-
.../nifi/remote/client/TestSiteInfoProvider.java | 2 +-
.../StandardSocketPeerIdentityProviderTest.java | 11 +-
.../socket/TestSocketClientTransaction.java | 4 +-
.../remote/util/SiteToSiteRestApiClientTest.java | 5 +
nifi-commons/nifi-swagger-integration/pom.xml | 2 +-
.../nifi/swagger/integration/StandardReader.java | 2 +-
nifi-commons/nifi-utils/pom.xml | 2 +-
.../nifi/remote/io/InterruptableInputStream.java | 4 +-
.../nifi/remote/io/InterruptableOutputStream.java | 4 +-
.../nifi/stream/io/util/AbstractDemarcator.java | 6 +-
.../nifi/stream/io/util/TextLineDemarcator.java | 3 +-
.../org/apache/nifi/util/ComponentIdGenerator.java | 14 +-
.../main/java/org/apache/nifi/util/DomUtils.java | 6 +-
.../util/concurrency/DebugEnabledTimedLock.java | 6 +-
.../apache/nifi/util/concurrency/TimedLock.java | 6 +-
.../java/org/apache/nifi/util/file/FileUtils.java | 4 +-
.../util/file/monitor/CompoundUpdateMonitor.java | 5 +
.../java/org/apache/nifi/util/search/Search.java | 4 +-
.../nifi/util/search/ahocorasick/AhoCorasick.java | 6 +-
.../apache/nifi/util/search/ahocorasick/Node.java | 4 +-
.../nifi/util/search/ahocorasick/SearchState.java | 4 +-
.../stream/io/ByteCountingInputStreamTest.java | 2 +-
.../stream/io/ByteCountingOutputStreamTest.java | 3 +-
.../nifi/stream/io/LimitingInputStreamTest.java | 2 +-
.../nifi/stream/io/util/StreamDemarcatorTest.java | 1 -
.../stream/io/util/TextLineDemarcatorTest.java | 2 +-
.../nifi/util/timebuffer/TestRingBuffer.java | 1 -
.../util/validator/TestStandardValidators.java | 5 +-
nifi-commons/nifi-uuid5/pom.xml | 2 +-
nifi-commons/nifi-web-client-api/pom.xml | 2 +-
nifi-commons/nifi-web-client/pom.xml | 4 +-
.../nifi/web/client/StandardWebClientService.java | 1 -
.../web/client/ssl/StandardSSLContextProvider.java | 10 +-
.../ssl/StandardSSLSocketFactoryProvider.java | 2 +-
.../org/apache/nifi/web/client/ssl/TlsContext.java | 2 +-
.../web/client/StandardWebClientServiceTest.java | 3 +-
nifi-commons/nifi-web-servlet-shared/pom.xml | 2 +-
.../nifi/web/servlet/shared/RequestUriBuilder.java | 1 +
.../web/servlet/shared/RequestUriBuilderTest.java | 2 +-
nifi-commons/nifi-write-ahead-log/pom.xml | 4 +-
.../apache/nifi/wali/LengthDelimitedJournal.java | 8 +-
.../apache/nifi/wali/TestBlockingQueuePool.java | 1 -
.../nifi/wali/TestLengthDelimitedJournal.java | 6 +-
.../src/test/java/org/wali/DummyRecordSerde.java | 12 +-
nifi-commons/nifi-xml-processing/pom.xml | 2 +-
.../parsers/StandardDocumentProvider.java | 6 +-
.../processing/sax/StandardInputSourceParser.java | 4 +-
.../stream/StandardXMLEventReaderProvider.java | 2 +-
.../stream/StandardXMLStreamReaderProvider.java | 2 +-
.../transform/StandardTransformProvider.java | 2 +-
.../validation/StandardSchemaValidator.java | 5 +-
.../parsers/StandardDocumentProviderTest.java | 5 +-
.../stream/StandardXMLEventReaderProviderTest.java | 4 +-
.../StandardXMLStreamReaderProviderTest.java | 4 +-
.../transform/StandardTransformProviderTest.java | 4 +-
.../validation/StandardSchemaValidatorTest.java | 5 +-
nifi-commons/pom.xml | 6 +-
.../nifi-connector-mock-api/pom.xml | 4 +-
.../nifi-connector-mock-server-nar/pom.xml | 6 +-
.../nifi-connector-mock-server/pom.xml | 12 +-
.../nifi-connector-mock-integration-tests/pom.xml | 18 +-
.../pom.xml | 4 +-
.../nifi-connector-mock-test-connectors/pom.xml | 4 +-
.../nifi-connector-mock-test-bundle/pom.xml | 2 +-
.../nifi-connector-mock/pom.xml | 24 +-
nifi-connector-mock-bundle/pom.xml | 2 +-
.../nifi-kafka-to-s3-connector/pom.xml | 2 +-
.../nifi/connectors/kafkas3/KafkaTopicsStep.java | 2 +-
.../src/main/resources/flows/Kafka_to_S3.json | 44 +-
.../nifi-kafka-to-s3-integration-tests/pom.xml | 36 +-
.../nifi-kafka-to-s3-nar/pom.xml | 4 +-
nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml | 2 +-
nifi-connectors/pom.xml | 6 +-
nifi-docker/dockerhub/pom.xml | 2 +-
nifi-docker/dockermaven/pom.xml | 2 +-
nifi-docker/pom.xml | 2 +-
nifi-docs/NOTICE | 2 +-
nifi-docs/pom.xml | 2 +-
nifi-extension-bom/pom.xml | 12 +-
.../nifi-airtable-bundle/nifi-airtable-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-airtable-processors/pom.xml | 4 +-
.../processors/airtable/QueryAirtableTable.java | 37 +-
.../parse/AirtableRecordSetFlowFileWriter.java | 7 +-
.../airtable/parse/AirtableRetrievePageResult.java | 3 +-
.../parse/AirtableRetrieveTableResult.java | 3 +-
.../airtable/parse/AirtableTableRetriever.java | 11 +-
.../airtable/service/AirtableRestService.java | 13 +-
.../airtable/TestQueryAirtableTable.java | 22 +
.../nifi-airtable-bundle/pom.xml | 2 +-
.../nifi-amqp-bundle/nifi-amqp-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-amqp-bundle/nifi-amqp-processors/pom.xml | 2 +-
.../apache/nifi/amqp/processors/AMQPConsumer.java | 15 +-
.../apache/nifi/amqp/processors/AMQPPublisher.java | 11 +-
.../apache/nifi/amqp/processors/AMQPResource.java | 4 +-
.../apache/nifi/amqp/processors/AMQPWorker.java | 9 +-
.../amqp/processors/AbstractAMQPProcessor.java | 21 +-
.../apache/nifi/amqp/processors/PublishAMQP.java | 6 +-
.../nifi/amqp/processors/AMQPConsumerTest.java | 27 +-
.../nifi/amqp/processors/AMQPPublisherTest.java | 25 +-
.../amqp/processors/AbstractAMQPProcessorTest.java | 1 -
.../nifi/amqp/processors/ConsumeAMQPTest.java | 46 +-
.../nifi/amqp/processors/PublishAMQPTest.java | 20 +
.../apache/nifi/amqp/processors/TestChannel.java | 28 +-
.../nifi/amqp/processors/TestConnection.java | 10 +-
nifi-extension-bundles/nifi-amqp-bundle/pom.xml | 2 +-
.../nifi-apicurio-bundle/nifi-apicurio-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-apicurio-schema-registry-service/pom.xml | 6 +-
.../nifi-apicurio-bundle/pom.xml | 2 +-
.../nifi-asana-processors-nar/pom.xml | 41 -
.../src/main/resources/META-INF/LICENSE | 202 -
.../src/main/resources/META-INF/NOTICE | 20 -
.../nifi-asana-processors/pom.xml | 70 -
.../nifi/processors/asana/AsanaObjectType.java | 107 -
.../nifi/processors/asana/GetAsanaObject.java | 437 -
.../nifi/processors/asana/MapStringSerDe.java | 66 -
.../apache/nifi/processors/asana/StringSerDe.java | 51 -
.../asana/utils/AbstractAsanaObjectFetcher.java | 49 -
.../nifi/processors/asana/utils/AsanaObject.java | 71 -
.../processors/asana/utils/AsanaObjectFetcher.java | 53 -
.../asana/utils/AsanaObjectFetcherException.java | 32 -
.../processors/asana/utils/AsanaObjectState.java | 23 -
.../asana/utils/AsanaProjectEventFetcher.java | 80 -
.../asana/utils/AsanaProjectFetcher.java | 40 -
.../asana/utils/AsanaProjectMembershipFetcher.java | 58 -
.../utils/AsanaProjectStatusAttachmentFetcher.java | 66 -
.../asana/utils/AsanaProjectStatusFetcher.java | 58 -
.../processors/asana/utils/AsanaStoryFetcher.java | 57 -
.../processors/asana/utils/AsanaTagFetcher.java | 35 -
.../asana/utils/AsanaTaskAttachmentFetcher.java | 62 -
.../processors/asana/utils/AsanaTaskFetcher.java | 105 -
.../asana/utils/AsanaTeamMemberFetcher.java | 58 -
.../processors/asana/utils/AsanaUserFetcher.java | 35 -
.../asana/utils/GenericAsanaObjectFetcher.java | 177 -
.../services/org.apache.nifi.processor.Processor | 15 -
.../additionalDetails.md | 73 -
.../asana/AbstractAsanaObjectFetcherTest.java | 89 -
.../nifi/processors/asana/AsanaObjectTest.java | 114 -
.../asana/AsanaProjectEventFetcherTest.java | 162 -
.../processors/asana/AsanaProjectFetcherTest.java | 150 -
.../asana/AsanaProjectMembershipFetcherTest.java | 190 -
.../AsanaProjectStatusAttachmentFetcherTest.java | 247 -
.../asana/AsanaProjectStatusFetcherTest.java | 181 -
.../processors/asana/AsanaStoryFetcherTest.java | 441 -
.../nifi/processors/asana/AsanaTagFetcherTest.java | 140 -
.../asana/AsanaTaskAttachmentFetcherTest.java | 470 -
.../processors/asana/AsanaTaskFetcherTest.java | 437 -
.../processors/asana/AsanaTeamFetcherTest.java | 142 -
.../asana/AsanaTeamMemberFetcherTest.java | 184 -
.../processors/asana/AsanaUserFetcherTest.java | 142 -
.../asana/GenericAsanaObjectFetcherTest.java | 213 -
.../asana/GetAsanaObjectConfigurationTest.java | 448 -
.../asana/GetAsanaObjectLifecycleTest.java | 297 -
.../nifi/processors/asana/MapStringSerDeTest.java | 66 -
.../nifi/processors/asana/StringSerDeTest.java | 54 -
.../mocks/MockAbstractAsanaObjectFetcher.java | 55 -
.../mocks/MockAsanaClientProviderService.java | 33 -
.../asana/mocks/MockDistributedMapCacheClient.java | 73 -
.../asana/mocks/MockGenericAsanaObjectFetcher.java | 36 -
.../processors/asana/mocks/MockGetAsanaObject.java | 34 -
.../nifi-asana-services-api-nar/pom.xml | 42 -
.../src/main/resources/META-INF/LICENSE | 228 -
.../src/main/resources/META-INF/NOTICE | 103 -
.../nifi-asana-services-api/pom.xml | 34 -
.../apache/nifi/controller/asana/AsanaClient.java | 194 -
.../controller/asana/AsanaClientException.java | 25 -
.../asana/AsanaClientProviderService.java | 32 -
.../controller/asana/AsanaEventsCollection.java | 41 -
.../nifi-asana-services-nar/pom.xml | 41 -
.../src/main/resources/META-INF/LICENSE | 202 -
.../src/main/resources/META-INF/NOTICE | 5 -
.../nifi-asana-bundle/nifi-asana-services/pom.xml | 61 -
.../nifi/controller/asana/StandardAsanaClient.java | 295 -
.../asana/StandardAsanaClientProviderService.java | 103 -
.../org.apache.nifi.controller.ControllerService | 15 -
.../additionalDetails.md | 68 -
.../StandardAsanaClientProviderServiceTest.java | 193 -
nifi-extension-bundles/nifi-asana-bundle/pom.xml | 64 -
.../nifi-asn1-bundle/nifi-asn1-nar/pom.xml | 6 +-
.../nifi-asn1-bundle/nifi-asn1-services/pom.xml | 2 +-
.../java/org/apache/nifi/jasn1/JASN1Reader.java | 117 +-
.../java/org/apache/nifi/jasn1/JASN1Utils.java | 2 +-
.../nifi/jasn1/convert/JASN1ConverterImpl.java | 2 +-
.../org/apache/nifi/jasn1/JASN1ReaderTest.java | 49 +-
.../TestJASN1RecordReaderWithComplexTypes.java | 6 +-
.../nifi/jasn1/TestRecordSchemaProvider.java | 1 -
nifi-extension-bundles/nifi-asn1-bundle/pom.xml | 2 +-
.../nifi-atlassian-extensions/pom.xml | 4 +-
.../nifi-atlassian-nar/pom.xml | 6 +-
.../nifi-atlassian-bundle/pom.xml | 2 +-
.../nifi-avro-bundle/nifi-avro-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-avro-bundle/nifi-avro-processors/pom.xml | 2 +-
.../nifi/processors/avro/ExtractAvroMetadata.java | 20 +-
.../org/apache/nifi/processors/avro/SplitAvro.java | 45 +-
.../apache/nifi/processors/avro/AvroTestUtil.java | 5 +-
nifi-extension-bundles/nifi-avro-bundle/pom.xml | 2 +-
.../nifi-aws-abstract-processors/pom.xml | 4 +-
.../processors/aws/AbstractAwsAsyncProcessor.java | 2 +-
.../nifi/processors/aws/AbstractAwsProcessor.java | 10 +-
.../processors/aws/AbstractAwsSyncProcessor.java | 2 +-
.../provider/PropertiesCredentialsProvider.java | 3 +-
.../aws/dynamodb/AbstractDynamoDBProcessor.java | 14 +-
.../aws/kinesis/KinesisProcessorUtils.java | 2 +-
.../ml/AbstractAwsMachineLearningJobStarter.java | 11 +-
.../nifi-aws-bundle/nifi-aws-kinesis-nar/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-aws-bundle/nifi-aws-kinesis/pom.xml | 4 +-
.../processors/aws/kinesis/ConsumeKinesis.java | 79 +-
.../aws/kinesis/ConsumeKinesisAttributes.java | 2 +-
.../aws/kinesis/MemoryBoundRecordBuffer.java | 89 +-
.../processors/aws/kinesis/ConsumeKinesisIT.java | 2 +-
.../processors/aws/kinesis/ConsumeKinesisTest.java | 16 -
.../aws/kinesis/MemoryBoundRecordBufferTest.java | 30 +-
.../aws/kinesis/ReaderRecordProcessorTest.java | 6 +-
.../nifi-aws-bundle/nifi-aws-nar/pom.xml | 12 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-aws-parameter-providers/pom.xml | 2 +-
.../aws/AwsSecretsManagerParameterProvider.java | 17 +-
.../TestAwsSecretsManagerParameterProvider.java | 53 +
.../nifi-aws-parameter-value-providers/pom.xml | 6 +-
.../AwsSecretsManagerParameterValueProvider.java | 6 +-
.../TestSecretsManagerParameterValueProvider.java | 35 +
.../nifi-aws-bundle/nifi-aws-processors/pom.xml | 22 +-
.../provider/factory/CredentialsStrategy.java | 1 -
.../AbstractBooleanCredentialsStrategy.java | 1 -
.../strategies/AbstractCredentialsStrategy.java | 6 +-
.../AccessKeyPairCredentialsStrategy.java | 1 -
.../strategies/AnonymousCredentialsStrategy.java | 1 -
.../strategies/AssumeRoleCredentialsStrategy.java | 2 +-
.../ExplicitDefaultCredentialsStrategy.java | 1 -
.../strategies/FileCredentialsStrategy.java | 1 -
.../ImplicitDefaultCredentialsStrategy.java | 1 -
.../NamedProfileCredentialsStrategy.java | 1 -
.../strategies/WebIdentityCredentialsStrategy.java | 3 +-
.../processors/aws/dynamodb/DeleteDynamoDB.java | 6 +-
.../nifi/processors/aws/dynamodb/GetDynamoDB.java | 6 +-
.../processors/aws/dynamodb/PutDynamoDBRecord.java | 2 +-
.../aws/kinesis/firehose/PutKinesisFirehose.java | 2 +-
.../record/AbstractKinesisRecordProcessor.java | 2 +-
.../stream/record/KinesisRecordProcessorRaw.java | 2 +-
.../record/KinesisRecordProcessorRecord.java | 2 +-
.../processors/aws/ml/textract/TextractType.java | 6 +-
.../aws/rds/AwsRdsIamDatabasePasswordProvider.java | 6 +-
.../nifi/processors/aws/s3/FetchS3Object.java | 2 +-
.../apache/nifi/processors/aws/s3/PutS3Object.java | 63 +-
.../encryption/ClientSideCEncryptionStrategy.java | 5 +-
.../ClientSideKMSEncryptionStrategy.java | 5 +-
.../encryption/S3EncryptionCommitmentPolicy.java | 74 +
.../aws/s3/encryption/S3EncryptionKeySpec.java | 5 +-
.../s3/encryption/StandardS3EncryptionService.java | 65 +-
.../org/apache/nifi/processors/aws/sqs/GetSQS.java | 1 -
.../ObsoleteAbstractAwsProcessorProperties.java} | 73 +-
.../aws/cloudwatch/ITPutCloudWatchMetric.java | 2 +-
.../aws/cloudwatch/MockPutCloudWatchMetric.java | 1 -
.../aws/cloudwatch/TestPutCloudWatchMetric.java | 45 +-
...WSCredentialsProviderControllerServiceTest.java | 53 +
.../provider/service/MockAWSProcessor.java | 1 -
.../aws/dynamodb/PutDynamoDBRecordTest.java | 42 +
.../aws/dynamodb/RecordToItemConverterTest.java | 2 +-
.../kinesis/stream/TestConsumeKinesisStream.java | 135 +-
.../aws/kinesis/stream/TestPutKinesisStream.java | 46 +-
.../nifi/processors/aws/lambda/TestPutLambda.java | 1 -
.../aws/ml/polly/GetAwsPollyStatusTest.java | 2 +-
.../aws/ml/polly/StartAwsPollyJobTest.java | 2 +-
.../ml/textract/GetAwsTextractJobStatusTest.java | 2 +-
.../ml/textract/StartAwsTextractJobStatusTest.java | 2 +-
.../transcribe/GetAwsTranscribeJobStatusTest.java | 2 +-
.../ml/transcribe/StartAwsTranscribeJobTest.java | 2 +-
.../ml/translate/GetAwsTranslateJobStatusTest.java | 2 +-
.../aws/ml/translate/StartAwsTranslateJobTest.java | 2 +-
.../nifi/processors/aws/s3/AbstractS3IT.java | 8 +-
.../nifi/processors/aws/s3/ITDeleteS3Object.java | 1 -
.../nifi/processors/aws/s3/ITFetchS3Object.java | 1 -
.../apache/nifi/processors/aws/s3/ITListS3.java | 1 -
.../nifi/processors/aws/s3/ITPutS3Object.java | 20 +-
.../nifi/processors/aws/s3/ITTagS3Object.java | 1 -
.../nifi/processors/aws/s3/TestDeleteS3Object.java | 1 -
.../nifi/processors/aws/s3/TestFetchS3Object.java | 1 -
.../apache/nifi/processors/aws/s3/TestListS3.java | 42 +-
.../nifi/processors/aws/s3/TestPutS3Object.java | 1 -
.../nifi/processors/aws/s3/TestTagS3Object.java | 1 -
.../aws/s3/encryption/S3EncryptionTestUtil.java | 7 +-
...ClientSideCEncryptionStrategyKeyValidation.java | 6 +-
.../s3/encryption/TestS3EncryptionStrategies.java | 10 +-
...ServerSideCEncryptionStrategyKeyValidation.java | 6 +-
.../TestStandardS3EncryptionService.java | 31 +-
.../apache/nifi/processors/aws/sns/ITPutSNS.java | 2 +-
.../apache/nifi/processors/aws/sns/TestPutSNS.java | 3 +-
.../nifi/processors/aws/sqs/ITDeleteSQS.java | 2 +-
.../nifi/processors/aws/sqs/TestDeleteSQS.java | 1 -
.../apache/nifi/processors/aws/sqs/TestGetSQS.java | 1 -
.../apache/nifi/processors/aws/sqs/TestPutSQS.java | 47 +-
.../processors/aws/util/LocalStackContainers.java | 2 +-
.../test/resources/mock-aws-credentials.properties | 2 +-
.../nifi-aws-bundle/nifi-aws-regions/pom.xml | 2 +-
.../nifi-aws-schema-registry-service/pom.xml | 6 +-
.../schemaregistry/AmazonGlueSchemaRegistry.java | 8 +-
.../serde/AWSSchemaRegistryConstants.java | 2 +-
...lueSchemaRegistryIncompatibleDataException.java | 2 +-
.../nifi-aws-service-api-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-aws-bundle/nifi-aws-service-api/pom.xml | 2 +-
nifi-extension-bundles/nifi-aws-bundle/pom.xml | 6 +-
.../nifi-azure-bundle/nifi-azure-nar/pom.xml | 12 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-azure-parameter-providers/pom.xml | 8 +-
.../AzureKeyVaultSecretsParameterProvider.java | 4 +
.../nifi-azure-processors/pom.xml | 32 +-
.../azure/ClientSideEncryptionSupport.java | 4 +-
.../document/AbstractAzureCosmosDBProcessor.java | 2 +-
.../cosmos/document/PutAzureCosmosDBRecord.java | 7 +-
.../azure/data/explorer/PutAzureDataExplorer.java | 2 +-
.../azure/eventhub/ConsumeAzureEventHub.java | 6 +-
.../azure/eventhub/GetAzureEventHub.java | 6 +-
.../azure/eventhub/PutAzureEventHub.java | 4 +-
.../azure/storage/CopyAzureBlobStorage_v12.java | 2 +-
.../azure/storage/DeleteAzureDataLakeStorage.java | 2 +-
.../azure/storage/ListAzureDataLakeStorage.java | 2 +-
.../azure/storage/MoveAzureDataLakeStorage.java | 2 +-
.../azure/storage/PutAzureBlobStorage_v12.java | 12 +
.../azure/storage/utils/AzureStorageUtils.java | 34 +
.../StandardAzureCredentialsControllerService.java | 67 +-
.../document/ITAbstractAzureCosmosDBDocument.java | 2 +-
.../azure/cosmos/document/MockTestBase.java | 2 +-
.../document/PutAzureCosmosDBRecordTest.java | 4 +-
.../azure/eventhub/GetAzureEventHubTest.java | 2 +-
.../storage/TestClientSideEncryptionSupport.java | 2 +-
.../azure/storage/TestPutAzureDataLakeStorage.java | 2 +-
.../utils/BlobServiceClientFactoryTest.java | 2 +-
.../utils/DataLakeServiceClientFactoryTest.java | 2 +-
.../explorer/StandardKustoIngestServiceTest.java | 32 +-
.../nifi-azure-registry-clients/pom.xml | 4 +-
.../azure/devops/AzureDevOpsRepositoryClient.java | 4 +-
.../nifi-azure-reporting-task/pom.xml | 6 +-
.../AbstractAzureLogAnalyticsReportingTask.java | 4 +-
.../AzureLogAnalyticsProvenanceReportingTask.java | 952 ++-
.../nifi/reporting/azure/loganalytics/Metric.java | 5 +-
.../azure/loganalytics/MetricsBuilder.java | 2 +-
.../api/AzureLogAnalyticsMetricsFactory.java | 20 +-
...stAzureLogAnalyticsProvenanceReportingTask.java | 6 +-
.../TestAzureLogAnalyticsReportingTask.java | 6 +-
.../azure/loganalytics/TestMetricsFactory.java | 2 +-
.../azure/loganalytics/TestVerification.java | 40 +-
.../nifi-azure-services-api-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-azure-services-api/pom.xml | 6 +-
.../cosmos/AzureCosmosDBConnectionService.java | 1 -
nifi-extension-bundles/nifi-azure-bundle/pom.xml | 25 +-
.../nifi-box-bundle/nifi-box-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-box-bundle/nifi-box-processors/pom.xml | 10 +-
.../apache/nifi/processors/box/BoxFileInfo.java | 8 +-
.../nifi/processors/box/BoxFlowFileAttribute.java | 3 +-
.../nifi/processors/box/ConsumeBoxEvents.java | 2 +-
.../apache/nifi/processors/box/FetchBoxFile.java | 30 +-
.../processors/box/GetBoxFileCollaborators.java | 2 +-
.../apache/nifi/processors/box/ListBoxFile.java | 33 +-
.../org/apache/nifi/processors/box/PutBoxFile.java | 15 +-
.../nifi/processors/box/FetchBoxFileInfoTest.java | 2 +-
.../nifi/processors/box/FetchBoxFileTest.java | 40 +-
.../nifi/processors/box/FileListingTestTrait.java | 11 +-
.../box/GetBoxFileCollaboratorsTest.java | 2 +-
.../processors/box/ListBoxFileListingTest.java | 17 +-
.../nifi/processors/box/ListBoxFileTest.java | 47 +-
.../apache/nifi/processors/box/PutBoxFileTest.java | 49 +-
.../nifi/processors/box/utils/BoxDateTest.java | 2 +-
.../nifi-box-services-api-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-box-bundle/nifi-box-services-api/pom.xml | 2 +-
.../nifi-box-bundle/nifi-box-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-box-bundle/nifi-box-services/pom.xml | 8 +-
.../DeveloperBoxClientService.java | 1 -
.../JsonConfigBasedBoxClientService.java | 20 +-
...nConfigBasedBoxClientServiceTestRunnerTest.java | 39 +-
nifi-extension-bundles/nifi-box-bundle/pom.xml | 4 +-
.../nifi-cdc/nifi-cdc-api/pom.xml | 2 +-
.../apache/nifi/cdc/event/BaseRowEventInfo.java | 1 -
.../apache/nifi/cdc/event/ColumnDefinition.java | 8 +-
.../java/org/apache/nifi/cdc/event/TableInfo.java | 8 +-
.../apache/nifi/cdc/event/TableInfoCacheKey.java | 10 +-
.../org/apache/nifi/cdc/event/io/EventWriter.java | 2 +-
.../nifi-cdc-mysql-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-cdc-mysql-processors/pom.xml | 8 +-
.../nifi/cdc/mysql/event/BinlogEventListener.java | 2 +-
.../apache/nifi/cdc/mysql/event/DDLEventInfo.java | 1 -
.../mysql/event/handler/CommitEventHandler.java | 2 +-
.../mysql/event/io/AbstractBinlogEventWriter.java | 2 +-
.../event/io/CommitTransactionEventWriter.java | 1 -
.../nifi/cdc/mysql/event/io/DDLEventWriter.java | 3 +-
.../nifi/cdc/mysql/event/io/DeleteRowsWriter.java | 4 +-
.../nifi/cdc/mysql/event/io/InsertRowsWriter.java | 5 +-
.../nifi/cdc/mysql/event/io/UpdateRowsWriter.java | 5 +-
.../cdc/mysql/processors/CaptureChangeMySQL.java | 3 +-
.../processors/ssl/BinaryLogSSLSocketFactory.java | 2 +-
.../apache/nifi/cdc/mysql/MockBinlogClient.java | 2 +-
.../cdc/mysql/event/io/TestInsertRowsWriter.java | 3 +-
.../mysql/processors/CaptureChangeMySQLTest.java | 66 +-
.../ssl/BinaryLogSSLSocketFactoryTest.java | 6 +-
.../nifi-cdc/nifi-cdc-mysql-bundle/pom.xml | 4 +-
nifi-extension-bundles/nifi-cdc/pom.xml | 2 +-
.../nifi-cipher-bundle/nifi-cipher-nar/pom.xml | 6 +-
.../nifi-cipher-processors/pom.xml | 2 +-
.../nifi/processors/cipher/VerifyContentMAC.java | 44 +-
.../processors/cipher/VerifyContentMACTest.java | 18 +-
nifi-extension-bundles/nifi-cipher-bundle/pom.xml | 2 +-
.../nifi-compress-bundle/nifi-compress-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-compress-processors/pom.xml | 4 +-
.../processors/compress/ModifyCompression.java | 4 +-
.../processors/compress/TestModifyCompression.java | 2 +-
.../nifi-compress-bundle/pom.xml | 2 +-
.../nifi-confluent-platform-nar/pom.xml | 2 +-
.../src/main/resource/META-INF/NOTICE | 2 +-
.../nifi-confluent-platform-schema-api/pom.xml | 2 +-
.../nifi-confluent-protobuf-antlr-parser/pom.xml | 2 +-
.../AntlrProtobufMessageSchemaReaderTest.java | 2 +-
.../pom.xml | 2 +-
.../nifi/confluent/schemaregistry/VarintUtils.java | 2 +-
.../ConfluentProtobufMessageNameResolverTest.java | 3 +-
.../confluent/schemaregistry/VarintUtilsTest.java | 2 +-
.../nifi-confluent-schema-registry-service/pom.xml | 4 +-
.../schemaregistry/ConfluentSchemaRegistry.java | 28 +-
.../client/RestSchemaRegistryClient.java | 12 +-
.../client/SchemaRegistryClient.java | 4 +-
.../ConfluentSchemaRegistryTest.java | 36 +-
.../client/RestSchemaRegistryClientTest.java | 9 +-
.../schemaregistry/client/SchemaResponse.java | 1 -
.../nifi-confluent-platform-bundle/pom.xml | 2 +-
.../nifi-couchbase-nar/pom.xml | 6 +-
.../nifi-couchbase-processors/pom.xml | 6 +-
.../couchbase/AbstractCouchbaseProcessor.java | 4 +-
.../nifi-couchbase-services-api-nar/pom.xml | 6 +-
.../nifi-couchbase-services-api/pom.xml | 2 +-
.../nifi-couchbase-standard-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-couchbase-standard-services/pom.xml | 10 +-
.../StandardCouchbaseConnectionService.java | 1 -
.../services/couchbase/TestCouchbaseClient.java | 1 -
.../nifi-couchbase-bundle/pom.xml | 2 +-
.../nifi-dropbox-processors-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-dropbox-processors/pom.xml | 14 +-
.../nifi/processors/dropbox/DropboxFileInfo.java | 25 +-
.../nifi/processors/dropbox/DropboxTrait.java | 13 +-
.../nifi/processors/dropbox/FetchDropbox.java | 43 +-
.../nifi/processors/dropbox/ListDropbox.java | 49 +-
.../apache/nifi/processors/dropbox/PutDropbox.java | 64 +-
.../processors/dropbox/AbstractDropboxTest.java | 17 +-
.../nifi/processors/dropbox/FetchDropboxTest.java | 19 +-
.../nifi/processors/dropbox/ListDropboxTest.java | 25 +-
.../nifi/processors/dropbox/PutDropboxTest.java | 37 +-
.../dropbox/TestDropboxProcessorMigration.java | 88 +
.../nifi-dropbox-services-api-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-dropbox-services-api/pom.xml | 2 +-
.../nifi-dropbox-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-dropbox-services/pom.xml | 4 +-
.../dropbox/StandardDropboxCredentialService.java | 3 +-
.../StandardDropboxCredentialServiceTest.java | 54 +
nifi-extension-bundles/nifi-dropbox-bundle/pom.xml | 2 +-
.../pom.xml | 6 +-
.../nifi-elasticsearch-client-service-api/pom.xml | 2 +-
.../nifi/elasticsearch/IndexOperationRequest.java | 2 +-
.../nifi-elasticsearch-client-service-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-elasticsearch-client-service/pom.xml | 14 +-
.../ElasticSearchClientServiceImpl.java | 2 +-
...estElasticSearchControllerServiceMigration.java | 123 +
.../integration/ElasticSearchClientService_IT.java | 2 +-
.../nifi-elasticsearch-restapi-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-elasticsearch-restapi-processors/pom.xml | 20 +-
.../elasticsearch/AbstractPutElasticsearch.java | 2 +-
.../elasticsearch/ConsumeElasticsearch.java | 20 +-
.../elasticsearch/PutElasticsearchJson.java | 4 +-
.../elasticsearch/PutElasticsearchRecord.java | 2 +-
.../elasticsearch/SearchElasticsearch.java | 2 +-
.../elasticsearch/api/BulkOperation.java | 2 +-
.../api/PaginatedJsonQueryParameters.java | 2 +-
.../AbstractByQueryElasticsearchTest.java | 12 +-
.../nifi/processors/elasticsearch/JsonUtils.java | 6 +-
.../elasticsearch/PutElasticsearchJsonTest.java | 2 +-
.../elasticsearch/PutElasticsearchRecordTest.java | 4 +-
.../TestElasticSearchProcessorMigration.java | 182 +
.../nifi-elasticsearch-test-utils/pom.xml | 2 +-
.../integration/AbstractElasticsearchITBase.java | 18 +-
.../nifi-elasticsearch-bundle/pom.xml | 8 +-
.../nifi-email-bundle/nifi-email-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-email-processors/pom.xml | 6 +-
.../processors/email/AbstractEmailProcessor.java | 23 +-
.../apache/nifi/processors/email/ConsumeIMAP.java | 8 +-
.../apache/nifi/processors/email/ConsumePOP3.java | 6 +-
.../processors/email/ExtractEmailAttachments.java | 20 +-
.../nifi/processors/email/ExtractEmailHeaders.java | 28 +-
.../nifi/processors/email/TestConsumeEmail.java | 12 +-
.../email/TestExtractEmailAttachments.java | 4 +-
nifi-extension-bundles/nifi-email-bundle/pom.xml | 2 +-
.../nifi-enrich-bundle/nifi-enrich-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-enrich-processors/pom.xml | 12 +-
.../apache/nifi/processors/GeoEnrichTestUtils.java | 1 +
nifi-extension-bundles/nifi-enrich-bundle/pom.xml | 2 +-
.../nifi-evtx-bundle/nifi-evtx-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-evtx-bundle/nifi-evtx-processors/pom.xml | 4 +-
.../nifi/processors/evtx/XmlBxmlNodeVisitor.java | 4 +-
.../processors/evtx/XmlBxmlNodeVisitorFactory.java | 2 +-
.../nifi/processors/evtx/XmlRootNodeHandler.java | 3 +-
.../nifi/processors/evtx/parser/BinaryReader.java | 1 +
.../parser/bxml/value/WStringArrayTypeNode.java | 7 +-
.../apache/nifi/processors/evtx/ParseEvtxTest.java | 2 +-
.../processors/evtx/XmlBxmlNodeVisitorTest.java | 4 +-
.../processors/evtx/XmlRootNodeHandlerTest.java | 2 +-
nifi-extension-bundles/nifi-evtx-bundle/pom.xml | 2 +-
.../nifi-extension-utils/nifi-bin-manager/pom.xml | 4 +-
.../org/apache/nifi/processor/util/bin/Bin.java | 2 +-
.../apache/nifi/processor/util/bin/BinFiles.java | 2 +-
.../nifi-conflict-resolution/pom.xml | 2 +-
.../resolution/ConflictResolutionStrategy.java | 7 +-
.../nifi-database-test-utils/pom.xml | 3 +-
.../apache/nifi/util/db/SimpleCommerceDataSet.java | 26 +-
.../nifi-database-utils/pom.xml | 21 +-
.../java/org/apache/nifi/util/db/JdbcCommon.java | 19 +-
.../nifi/util/db/AbstractConnectionTest.java | 52 +
.../apache/nifi/util/db/TestJdbcClobReadable.java | 59 +-
.../org/apache/nifi/util/db/TestJdbcCommon.java | 214 +-
.../nifi/util/db/TestJdbcCommonConvertToAvro.java | 10 +-
.../apache/nifi/util/db/TestJdbcHugeStream.java | 75 +-
.../nifi-extension-utils/nifi-dbcp-base/pom.xml | 14 +-
.../nifi/dbcp/AbstractDBCPConnectionPool.java | 19 +-
.../nifi/dbcp/ProviderAwareBasicDataSource.java | 2 +-
.../org/apache/nifi/dbcp/utils/DBCPProperties.java | 4 +
.../nifi/dbcp/utils/DataSourceConfiguration.java | 2 +-
.../nifi/dbcp/utils/DefaultDataSourceValues.java | 3 +-
.../nifi/dbcp/AbstractDBCPConnectionPoolTest.java | 4 +-
.../nifi-extension-utils/nifi-dbcp-utils/pom.xml | 2 +-
.../org/apache/nifi/dbcp/utils/DriverUtils.java | 2 +-
.../pom.xml | 21 +-
.../EmbeddedDatabaseConnectionService.java | 55 +-
.../nifi-extension-utils/nifi-event-listen/pom.xml | 6 +-
.../nifi/processor/util/listen/EventBatcher.java | 2 +-
.../processor/util/listen/FlowFileEventBatch.java | 30 +-
.../dispatcher/DatagramChannelDispatcher.java | 10 +-
.../processor/util/listen/event/EventFactory.java | 1 +
.../util/listen/event/EventFactoryUtil.java | 3 +-
.../util/listen/event/NetworkEventFactory.java | 2 +-
.../processor/util/listen/event/StandardEvent.java | 10 +
.../util/listen/event/StandardEventFactory.java | 4 +-
.../listen/queue/TrackingLinkedBlockingQueue.java | 1 -
.../processor/util/listen/EventBatcherTest.java | 6 +-
.../nifi-extension-utils/nifi-event-put/pom.xml | 6 +-
.../util/put/AbstractPutEventProcessor.java | 2 +-
.../nifi-event-transport/pom.xml | 8 +-
.../apache/nifi/event/transport/NetworkEvent.java | 2 +-
.../ByteArrayMessageNettyEventServerFactory.java | 2 +-
.../netty/ByteArrayNettyEventSenderFactory.java | 1 +
.../transport/netty/NettyEventSenderFactory.java | 2 +-
.../transport/netty/NettyEventServerFactory.java | 4 +-
.../netty/StringNettyEventSenderFactory.java | 2 +-
.../ssl/ClientSslStandardChannelInitializer.java | 4 +-
.../ssl/ServerSslHandlerChannelInitializer.java | 6 +-
.../netty/codec/SocketByteArrayMessageDecoder.java | 6 +-
.../netty/NettyEventSenderFactoryTest.java | 2 +-
.../netty/StringNettyEventSenderFactoryTest.java | 4 +-
.../nifi-file-transfer/pom.xml | 8 +-
.../processor/util/file/transfer/FileInfo.java | 14 +-
.../processor/util/file/transfer/FileTransfer.java | 12 +-
.../nifi-git-flow-registry/pom.xml | 7 +-
.../flow/git/AbstractGitFlowRegistryClient.java | 9 +
.../JacksonFlowSnapshotSerializerTest.java | 2 +-
.../nifi-extension-utils/nifi-hadoop-utils/pom.xml | 8 +-
.../java/org/apache/nifi/hadoop/SecurityUtil.java | 4 +-
.../processors/hadoop/AbstractHadoopProcessor.java | 97 +-
.../nifi/processors/hadoop/CompressionType.java | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-listed-entity/pom.xml | 10 +-
.../processor/util/list/AbstractListProcessor.java | 4 +-
.../nifi/processor/util/list/EntityListing.java | 6 +-
.../nifi-migration-utils/pom.xml | 2 +-
.../nifi-extension-utils/nifi-put-pattern/pom.xml | 2 +-
.../nifi/processor/util/pattern/ErrorTypes.java | 12 +-
.../processor/util/pattern/ExceptionHandler.java | 2 +-
.../processor/util/pattern/PartialFunctions.java | 2 +-
.../util/pattern/TestRollbackOnFailure.java | 8 +-
.../nifi-record-path-property/pom.xml | 6 +-
.../nifi-avro-record-utils/pom.xml | 10 +-
.../java/org/apache/nifi/avro/AvroTypeUtil.java | 59 +-
.../schema/access/InferenceSchemaStrategy.java | 4 +-
.../org/apache/nifi/avro/TestAvroTypeUtil.java | 94 +-
.../schema/access/InferenceSchemaStrategyTest.java | 4 +-
.../nifi-hadoop-record-utils/pom.xml | 10 +-
.../processors/hadoop/AbstractFetchHDFSRecord.java | 2 +-
.../processors/hadoop/AbstractPutHDFSRecord.java | 2 +-
.../nifi-json-record-utils/pom.xml | 8 +-
.../org/apache/nifi/json/JsonRecordSource.java | 4 +-
.../apache/nifi/json/JsonTreeRowRecordReader.java | 4 +-
.../nifi-mock-record-utils/pom.xml | 12 +-
.../record/CommaSeparatedRecordReader.java | 2 +-
.../serialization/record/MockRecordParser.java | 2 +-
.../serialization/record/MockRecordWriter.java | 2 +-
.../serialization/record/MockSchemaRegistry.java | 2 +-
.../nifi-schema-inference-utils/pom.xml | 6 +-
.../nifi-standard-record-utils/pom.xml | 10 +-
.../main/java/org/apache/nifi/csv/CSVUtils.java | 3 +-
.../java/org/apache/nifi/csv/CSVValidators.java | 2 +-
.../schema/access/InheritSchemaFromRecord.java | 4 +-
.../nifi/schema/access/NopSchemaAccessWriter.java | 4 +-
.../nifi/schema/access/SchemaAccessWriter.java | 4 +-
.../nifi/schema/access/SchemaNameAsAttribute.java | 6 +-
.../validation/StandardSchemaValidationResult.java | 6 +-
.../schema/validation/StandardValidationError.java | 6 +-
.../java/org/apache/nifi/csv/CSVUtilsTest.java | 7 +-
.../access/AbstractSchemaAccessStrategyTest.java | 2 +-
.../serialization/SplitRecordSetHandlerTest.java | 2 +-
.../nifi-yaml-record-utils/pom.xml | 6 +-
.../nifi-extension-utils/nifi-record-utils/pom.xml | 2 +-
.../nifi-reporting-utils/pom.xml | 4 +-
.../reporting/util/metrics/MetricsService.java | 21 +-
.../reporting/util/metrics/api/MetricBuilder.java | 2 +-
.../reporting/util/metrics/api/MetricsBuilder.java | 8 +-
.../nifi-resource-transfer/pom.xml | 4 +-
.../nifi-service-utils/pom.xml | 2 +-
.../nifi-extension-utils/nifi-syslog-utils/pom.xml | 2 +-
.../apache/nifi/syslog/parsers/SyslogParser.java | 4 +-
.../StrictSyslog5424ParserDashPolicyTest.java | 1 -
.../StrictSyslog5424ParserNullPolicyTest.java | 1 -
.../StrictSyslog5424ParserOmitPolicyTest.java | 1 -
.../nifi-extension-utils/pom.xml | 3 +-
.../nifi-flow-registry-client-nar/pom.xml | 6 +-
.../nifi-flow-registry-client-services/pom.xml | 8 +-
.../flow/NifiRegistryFlowRegistryClient.java | 4 +-
.../nifi-flow-registry-client-bundle/pom.xml | 2 +-
.../nifi-gcp-bundle/nifi-gcp-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-gcp-parameter-providers/pom.xml | 6 +-
.../gcp/TestGcpSecretManagerParameterProvider.java | 2 +-
.../nifi-gcp-bundle/nifi-gcp-processors/pom.xml | 18 +-
.../nifi/processors/gcp/AbstractGCPProcessor.java | 7 +-
.../processors/gcp/ProxyAwareTransportFactory.java | 2 +-
.../processors/gcp/bigquery/proto/ProtoUtils.java | 182 +-
.../ApplicationDefaultCredentialsStrategy.java | 1 -
.../JsonFileServiceAccountCredentialsStrategy.java | 1 -
...sonStringServiceAccountCredentialsStrategy.java | 1 -
.../processors/gcp/drive/FetchGoogleDrive.java | 18 +-
.../processors/gcp/drive/GoogleDriveTrait.java | 4 +-
.../nifi/processors/gcp/drive/ListGoogleDrive.java | 2 +-
.../nifi/processors/gcp/drive/PutGoogleDrive.java | 2 +-
.../gcp/pubsub/AbstractGCPubSubProcessor.java | 338 +-
.../processors/gcp/pubsub/ConsumeGCPubSub.java | 12 +-
.../processors/gcp/pubsub/PublishGCPubSub.java | 4 +-
.../processors/gcp/storage/DeleteGCSObject.java | 1 -
.../processors/gcp/storage/FetchGCSObject.java | 29 +-
.../nifi/processors/gcp/storage/PutGCSObject.java | 1 -
.../apache/nifi/processors/gcp/storage/Util.java | 2 +-
.../gcp/vision/AbstractGcpVisionProcessor.java | 10 +-
...bstractGetGcpVisionAnnotateOperationStatus.java | 17 +-
.../vision/AbstractStartGcpVisionOperation.java | 15 +-
.../StartGcpVisionAnnotateFilesOperation.java | 8 +-
.../StartGcpVisionAnnotateImagesOperation.java | 8 +-
.../processors/gcp/bigquery/PutBigQueryIT.java | 38 +-
.../processors/gcp/bigquery/PutBigQueryTest.java | 13 +-
.../factory/MockCredentialsFactoryProcessor.java | 1 -
.../service/GCPCredentialsServiceTest.java | 1 -
.../service/MockCredentialsServiceProcessor.java | 1 -
.../gcp/drive/AbstractGoogleDriveTest.java | 22 +-
.../processors/gcp/drive/FetchGoogleDriveIT.java | 21 +-
.../processors/gcp/drive/FetchGoogleDriveTest.java | 22 +-
.../gcp/drive/ListGoogleDriveTestRunnerTest.java | 56 +-
.../processors/gcp/drive/PutGoogleDriveIT.java | 18 +-
.../processors/gcp/drive/PutGoogleDriveTest.java | 35 +-
.../nifi/processors/gcp/storage/AbstractGCSIT.java | 2 +-
.../processors/gcp/storage/AbstractGCSTest.java | 1 -
.../gcp/storage/DeleteGCSObjectTest.java | 5 +-
.../processors/gcp/storage/FetchGCSObjectTest.java | 19 +-
.../processors/gcp/storage/ListGCSBucketTest.java | 2 +-
.../processors/gcp/storage/PutGCSObjectIT.java | 2 +-
.../processors/gcp/storage/PutGCSObjectTest.java | 4 +-
.../gcp/storage/StorageAttributesTest.java | 2 +-
...tGcpVisionAnnotateFilesOperationStatusTest.java | 19 +-
...GcpVisionAnnotateImagesOperationStatusTest.java | 19 +-
.../StartGcpVisionAnnotateFilesOperationTest.java | 27 +-
.../StartGcpVisionAnnotateImagesOperationTest.java | 25 +-
.../nifi-gcp-services-api-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-gcp-bundle/nifi-gcp-services-api/pom.xml | 2 +-
nifi-extension-bundles/nifi-gcp-bundle/pom.xml | 4 +-
.../nifi-geohash-bundle/nifi-geohash-nar/pom.xml | 6 +-
.../nifi-geohash-processors/pom.xml | 6 +-
.../nifi/processors/geohash/GeohashRecord.java | 20 +-
.../nifi/processors/geohash/GeohashRecordTest.java | 2 +-
nifi-extension-bundles/nifi-geohash-bundle/pom.xml | 2 +-
.../nifi-github-extensions/pom.xml | 4 +-
.../nifi/github/GitHubFlowRegistryClient.java | 2 +
.../apache/nifi/github/GitHubRepositoryClient.java | 19 +-
.../nifi-github-bundle/nifi-github-nar/pom.xml | 6 +-
nifi-extension-bundles/nifi-github-bundle/pom.xml | 2 +-
.../nifi-gitlab-extensions/pom.xml | 4 +-
.../nifi/gitlab/GitLabFlowRegistryClient.java | 2 +
.../apache/nifi/gitlab/GitLabRepositoryClient.java | 31 +-
.../nifi-gitlab-bundle/nifi-gitlab-nar/pom.xml | 6 +-
nifi-extension-bundles/nifi-gitlab-bundle/pom.xml | 2 +-
.../nifi-graph-client-service-api-nar/pom.xml | 6 +-
.../nifi-graph-client-service-api/pom.xml | 2 +-
.../org/apache/nifi/graph/GraphClientService.java | 2 +-
.../nifi-graph-bundle/nifi-graph-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-graph-processors/pom.xml | 12 +-
.../processors/graph/AbstractGraphExecutor.java | 2 +-
.../nifi/processors/graph/ExecuteGraphQuery.java | 4 +-
.../processors/graph/ExecuteGraphQueryRecord.java | 4 +-
.../graph/ExecuteGraphQueryRecordTest.java | 8 +-
.../processors/graph/TestExecuteGraphQuery.java | 2 +-
.../processors/graph/util/InMemoryGraphClient.java | 8 +-
.../nifi-graph-test-clients/pom.xml | 4 +-
.../graph/InMemoryJanusGraphClientService.java | 4 +-
.../nifi-neo4j-cypher-service-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-neo4j-cypher-service/pom.xml | 12 +-
.../nifi/graph/Neo4JCypherClientService.java | 4 +-
.../nifi/graph/ITNeo4JCypherClientServiceSSL.java | 5 +-
.../nifi/graph/ITNeo4JCypherExecutorNoSSL.java | 4 +-
.../nifi-other-graph-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-other-graph-services/pom.xml | 8 +-
.../apache/nifi/graph/TinkerpopClientService.java | 1 -
.../org/apache/nifi/graph/gremlin/SimpleEntry.java | 2 +-
nifi-extension-bundles/nifi-graph-bundle/pom.xml | 2 +-
.../nifi-groovyx-bundle/nifi-groovyx-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-groovyx-processors/pom.xml | 14 +-
.../processors/groovyx/ExecuteGroovyScript.java | 25 +-
.../nifi/processors/groovyx/GroovyMethods.java | 6 +-
.../processors/groovyx/flow/GroovySessionFile.java | 18 +-
.../groovyx/flow/ProcessSessionWrap.java | 25 +-
.../nifi/processors/groovyx/flow/SessionFile.java | 14 +-
.../apache/nifi/processors/groovyx/sql/OSql.java | 10 +-
.../apache/nifi/processors/groovyx/util/Files.java | 14 +-
.../nifi/processors/groovyx/util/Throwables.java | 8 +-
.../groovyx/ExecuteGroovyScriptTest.java | 127 +-
.../groovy/test_sql_04_insert_and_json.groovy | 2 +-
nifi-extension-bundles/nifi-groovyx-bundle/pom.xml | 2 +-
.../nifi-hadoop-bundle/nifi-hadoop-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-hdfs-processors/pom.xml | 12 +-
.../hadoop/HDFSExternalResourceProvider.java | 2 +-
.../apache/nifi/processors/hadoop/DeleteHDFS.java | 2 +-
.../apache/nifi/processors/hadoop/FetchHDFS.java | 2 +-
.../FlowFileStreamUnpackerSequenceFileWriter.java | 17 +-
.../org/apache/nifi/processors/hadoop/GetHDFS.java | 120 +-
.../nifi/processors/hadoop/GetHDFSFileInfo.java | 26 +-
.../apache/nifi/processors/hadoop/ListHDFS.java | 2 +-
.../apache/nifi/processors/hadoop/MoveHDFS.java | 8 +-
.../processors/hadoop/SequenceFileWriterImpl.java | 3 +-
.../hadoop/TarUnpackerSequenceFileWriter.java | 7 +-
.../hadoop/ZipUnpackerSequenceFileWriter.java | 11 +-
.../GSSExceptionRollbackYieldSessionHandler.java | 2 +-
.../nifi/processors/hadoop/util/HDFSListing.java | 6 +-
.../hadoop/util/InputStreamWritable.java | 3 +-
.../nifi/processors/hadoop/util/LongSerDe.java | 2 +-
.../hadoop/util/OutputStreamWritable.java | 5 +-
.../nifi/processors/hadoop/util/StringSerDe.java | 2 +-
.../nifi/nar/hadoop/util/ExtensionFilterTest.java | 2 +-
.../processors/hadoop/GetHDFSSequenceFileTest.java | 2 +-
.../nifi/processors/hadoop/MoveHDFSTest.java | 42 +-
.../apache/nifi/processors/hadoop/PutHDFSTest.java | 24 +-
.../hadoop/TestCreateHadoopSequenceFile.java | 4 +-
.../processors/hadoop/TestGetHDFSFileInfo.java | 27 +-
.../hadoop/inotify/util/EventTestUtils.java | 1 -
.../processors/hadoop/util/MockFileSystem.java | 2 +-
nifi-extension-bundles/nifi-hadoop-bundle/pom.xml | 2 +-
.../nifi-hadoop-libraries-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-hadoop-libraries-bundle/pom.xml | 2 +-
.../pom.xml | 6 +-
.../pom.xml | 4 +-
.../nifi-hashicorp-vault-client-service/pom.xml | 8 +-
.../StandardHashiCorpVaultClientService.java | 1 -
.../TestStandardHashiCorpVaultClientService.java | 54 +-
.../nifi-hashicorp-vault-nar/pom.xml | 12 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../pom.xml | 6 +-
.../hashicorp/HashiCorpVaultParameterProvider.java | 2 +-
.../pom.xml | 8 +-
.../nifi-hashicorp-vault-bundle/pom.xml | 2 +-
.../nifi-hazelcast-services-api-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-hazelcast-services-api/pom.xml | 2 +-
.../nifi-hazelcast-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-hazelcast-services/pom.xml | 4 +-
.../services/cache/HashMapHazelcastCache.java | 2 +-
.../cacheclient/HazelcastMapCacheClientTest.java | 17 +-
.../EmbeddedHazelcastCacheManagerTest.java | 7 +-
.../nifi-hazelcast-bundle/pom.xml | 2 +-
.../nifi-hl7-bundle/nifi-hl7-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-hl7-bundle/nifi-hl7-processors/pom.xml | 6 +-
.../nifi/processors/hl7/ExtractHL7Attributes.java | 20 +-
.../org/apache/nifi/processors/hl7/RouteHL7.java | 23 +-
nifi-extension-bundles/nifi-hl7-bundle/pom.xml | 2 +-
.../nifi-hubspot-bundle/nifi-hubspot-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-hubspot-processors/pom.xml | 4 +-
nifi-extension-bundles/nifi-hubspot-bundle/pom.xml | 2 +-
.../nifi-iceberg-aws-nar/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-iceberg-bundle/nifi-iceberg-aws/pom.xml | 2 +-
.../nifi-iceberg-azure-nar/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-iceberg-bundle/nifi-iceberg-azure/pom.xml | 2 +-
.../nifi-iceberg-parquet-writer-nar/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-iceberg-parquet-writer/pom.xml | 4 +-
.../iceberg/parquet/ParquetIcebergWriter.java | 12 +-
.../parquet/io/ParquetIcebergRowWriter.java | 2 +-
.../iceberg/parquet/ParquetIcebergWriterTest.java | 2 +-
.../nifi-iceberg-processors-nar/pom.xml | 2 +-
.../nifi-iceberg-processors/pom.xml | 2 +-
.../nifi/processors/iceberg/PutIcebergRecord.java | 12 +-
.../processors/iceberg/PutIcebergRecordTest.java | 10 +-
.../nifi-iceberg-rest-catalog-nar/pom.xml | 2 +-
.../nifi-iceberg-rest-catalog/pom.xml | 2 +-
.../iceberg/catalog/RESTIcebergCatalog.java | 6 +-
.../nifi-iceberg-service-api-nar/pom.xml | 2 +-
.../nifi-iceberg-service-api/pom.xml | 2 +-
.../nifi-iceberg-shared-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
nifi-extension-bundles/nifi-iceberg-bundle/pom.xml | 6 +-
.../nifi-iotdb-bundle/nifi-iotdb-nar/pom.xml | 6 +-
.../nifi-iotdb-processors/pom.xml | 4 +-
.../org/apache/nifi/processors/AbstractIoTDB.java | 48 +-
.../org/apache/nifi/processors/PutIoTDBRecord.java | 28 +-
.../apache/nifi/processors/QueryIoTDBRecord.java | 20 +-
.../nifi/processors/model/DatabaseField.java | 8 +-
.../nifi/processors/model/DatabaseSchema.java | 12 +-
.../apache/nifi/processors/AbstractIoTDBTest.java | 13 +-
.../apache/nifi/processors/PutIoTDBRecordTest.java | 3 +-
nifi-extension-bundles/nifi-iotdb-bundle/pom.xml | 2 +-
.../nifi-jms-cf-service-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-jms-bundle/nifi-jms-cf-service/pom.xml | 2 +-
.../cf/AbstractJMSConnectionFactoryProvider.java | 10 +-
.../jms/cf/CachedJMSConnectionFactoryHandler.java | 3 +-
.../nifi-jms-processors-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-jms-bundle/nifi-jms-processors/pom.xml | 6 +-
.../nifi/jms/cf/JMSConnectionFactoryHandler.java | 19 +-
.../nifi/jms/cf/JMSConnectionFactoryProvider.java | 3 +-
.../jms/cf/JndiJmsConnectionFactoryHandler.java | 8 +-
.../jms/cf/JndiJmsConnectionFactoryProvider.java | 3 +-
.../main/java/org/apache/nifi/jms/cf/Utils.java | 2 +-
.../nifi/jms/processors/AbstractJMSProcessor.java | 4 +-
.../org/apache/nifi/jms/processors/ConsumeJMS.java | 12 +-
.../apache/nifi/jms/processors/JMSConsumer.java | 18 +-
.../apache/nifi/jms/processors/JMSPublisher.java | 44 +-
.../org/apache/nifi/jms/processors/JMSWorker.java | 4 +-
.../processors/MessageBodyToBytesConverter.java | 19 +-
.../org/apache/nifi/jms/processors/PublishJMS.java | 6 +-
.../ioconcept/writer/record/RecordWriter.java | 2 +-
.../jms/cf/JMSConnectionFactoryHandlerForTest.java | 2 +-
.../jms/cf/JMSConnectionFactoryProviderTest.java | 2 +-
.../org/apache/nifi/jms/processors/CommonTest.java | 1 -
.../jms/processors/ConnectionFactoryConfigIT.java | 5 +-
.../apache/nifi/jms/processors/ConsumeJMSIT.java | 24 +-
.../nifi/jms/processors/ConsumeJMSManualTest.java | 15 +-
.../jms/processors/JMSPublisherConsumerIT.java | 22 +-
.../apache/nifi/jms/processors/PublishJMSIT.java | 14 +-
.../ConnectionFactoryInvocationHandler.java | 11 +-
.../helpers/ConnectionInvocationHandler.java | 11 +-
.../helpers/MessageProducerInvocationHandler.java | 9 +-
.../helpers/SessionInvocationHandler.java | 11 +-
nifi-extension-bundles/nifi-jms-bundle/pom.xml | 2 +-
.../nifi-jolt-bundle/nifi-jolt-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-jolt-bundle/nifi-jolt-processors/pom.xml | 15 +-
.../processors/jolt/AbstractJoltTransform.java | 17 +-
.../nifi/processors/jolt/JoltTransformJSON.java | 16 +-
.../nifi/processors/jolt/JoltTransformRecord.java | 20 +-
.../processors/jolt/TestJoltTransformJSON.java | 59 +-
.../processors/jolt/TestJoltTransformRecord.java | 36 +-
.../TestCustomJoltTransform.jar | Bin 1025 -> 0 bytes
.../TestCustomJoltTransform.jar | Bin 1025 -> 0 bytes
.../src/test/resources/specs/customChainrSpec.json | 4 +-
.../nifi-jolt-transform-json-ui/pom.xml | 12 +-
.../web/standard/api/AbstractStandardResource.java | 2 -
.../standard/api/processor/ProcessorResource.java | 14 +-
.../standard/api/processor/ProcessorWebUtils.java | 8 +-
.../api/transformjson/TransformJSONResource.java | 14 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../api/processor/TestProcessorResource.java | 14 +-
.../api/processor/TestProcessorWebUtils.java | 8 +-
.../transformjson/TestTransformJSONResource.java | 26 +-
.../nifi-jolt-bundle/nifi-jolt-utils/pom.xml | 24 +-
.../apache/nifi/jolt/util/TransformFactory.java | 23 +-
.../org/apache/nifi/jolt/util/TransformUtils.java | 8 +-
.../nifi/jolt/util/TestTransformFactory.java | 47 +-
.../jolt/CustomTransformJarProvider.java | 53 +
.../processors/jolt}/TestCustomJoltTransform.java | 19 +-
.../TestCustomJoltTransform.jar | Bin 1025 -> 0 bytes
nifi-extension-bundles/nifi-jolt-bundle/pom.xml | 14 +-
.../nifi-jslt-bundle/nifi-jslt-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-jslt-bundle/nifi-jslt-processors/pom.xml | 2 +-
.../nifi/processors/jslt/JSLTTransformJSON.java | 2 +-
.../processors/jslt/TestJSLTTransformJSON.java | 2 +-
nifi-extension-bundles/nifi-jslt-bundle/pom.xml | 2 +-
.../nifi-kafka-3-integration/pom.xml | 12 +-
.../nifi/kafka/processors/AbstractKafkaBaseIT.java | 2 +-
.../ConsumeKafkaInjectMetadataRecordIT.java | 2 +-
.../ConsumeKafkaInjectOffsetRecordIT.java | 2 +-
.../processors/ConsumeKafkaRecordWithNullIT.java | 6 +-
.../processors/ConsumeKafkaWrapperRecordIT.java | 2 +-
.../processors/PublishKafkaExpressionKeyIT.java | 4 +-
.../processors/PublishKafkaOneLargePayloadIT.java | 4 +-
.../processors/PublishKafkaTooLargePayloadIT.java | 6 +-
.../publish/PublishKafkaMultipleFFIT.java | 2 +-
.../additional/PublishKafkaContentX1IT.java | 2 +-
.../additional/PublishKafkaWrapperX1IT.java | 2 +-
.../additional/PublishKafkaWrapperX2IT.java | 2 +-
.../additional/PublishKafkaWrapperX3IT.java | 2 +-
.../additional/PublishKafkaWrapperX4IT.java | 2 +-
.../nifi-kafka-3-service-nar/pom.xml | 6 +-
.../nifi-kafka-bundle/nifi-kafka-3-service/pom.xml | 4 +-
.../nifi-kafka-code-coverage/pom.xml | 12 +-
.../nifi-kafka-bundle/nifi-kafka-nar/pom.xml | 6 +-
.../nifi-kafka-processors/pom.xml | 8 +-
.../producer/convert/KafkaRecordConverterTest.java | 2 +-
.../nifi-kafka-service-api-nar/pom.xml | 6 +-
.../nifi-kafka-service-api/pom.xml | 2 +-
.../nifi-kafka-service-aws-nar/pom.xml | 2 +-
.../nifi-kafka-service-aws/pom.xml | 4 +-
.../service/aws/AmazonMSKConnectionService.java | 3 +-
.../aws/AmazonMSKCredentialsCallbackHandler.java | 6 +-
.../AmazonMSKCredentialsCallbackHandlerTest.java | 6 +-
.../nifi-kafka-service-shared/pom.xml | 12 +-
.../kafka/service/Kafka3ConnectionService.java | 44 +-
.../kafka/service/KafkaConnectionVerifier.java | 317 +
.../kafka/service/consumer/RecordIterable.java | 2 +-
.../security/OAuthBearerLoginCallbackHandler.java | 7 +-
.../service/Kafka3ConnectionServiceBaseIT.java | 87 +-
.../nifi-kafka-bundle/nifi-kafka-shared/pom.xml | 2 +-
.../shared/login/KerberosLoginConfigProvider.java | 2 +-
.../kafka/shared/login/LoginConfigBuilder.java | 2 +-
.../login/AwsMskIamLoginConfigProviderTest.java | 4 +-
.../kafka/shared/login/LoginConfigBuilderTest.java | 2 +-
nifi-extension-bundles/nifi-kafka-bundle/pom.xml | 2 +-
.../nifi-media-bundle/nifi-media-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-media-processors/pom.xml | 4 +-
.../processors/image/ExtractImageMetadata.java | 19 +-
.../apache/nifi/processors/image/ResizeImage.java | 36 +-
.../processors/media/ExtractMediaMetadata.java | 19 +-
.../document/ExtractDocumentTextTest.java | 2 +-
.../processors/image/ExtractImageMetadataTest.java | 11 +-
.../nifi/processors/image/TestResizeImage.java | 4 +-
.../processors/media/TestExtractMediaMetadata.java | 2 +-
nifi-extension-bundles/nifi-media-bundle/pom.xml | 2 +-
.../nifi-mongodb-client-service-api-nar/pom.xml | 6 +-
.../nifi-mongodb-client-service-api/pom.xml | 2 +-
.../apache/nifi/mongodb/MongoDBClientService.java | 46 +-
.../nifi-mongodb-bundle/nifi-mongodb-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-mongodb-processors/pom.xml | 12 +-
.../processors/mongodb/AbstractMongoProcessor.java | 2 +-
.../nifi/processors/mongodb/DeleteMongo.java | 8 +-
.../apache/nifi/processors/mongodb/GetMongo.java | 6 +-
.../apache/nifi/processors/mongodb/PutMongo.java | 4 +-
.../processors/mongodb/PutMongoBulkOperations.java | 4 +-
.../nifi/processors/mongodb/PutMongoRecord.java | 4 +-
.../processors/mongodb/RunMongoAggregation.java | 4 +-
.../processors/mongodb/gridfs/FetchGridFS.java | 1 +
.../nifi/processors/mongodb/PutMongoRecordIT.java | 1 -
.../nifi-mongodb-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-mongodb-services/pom.xml | 8 +-
.../nifi/mongodb/MongoDBControllerService.java | 3 +-
nifi-extension-bundles/nifi-mongodb-bundle/pom.xml | 4 +-
.../nifi-mqtt-bundle/nifi-mqtt-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-mqtt-bundle/nifi-mqtt-processors/pom.xml | 10 +-
.../apache/nifi/processors/mqtt/ConsumeMQTT.java | 41 +-
.../mqtt/adapters/HiveMqV5ClientAdapter.java | 4 +-
.../mqtt/adapters/PahoMqttClientAdapter.java | 2 +-
.../processors/mqtt/common/MqttClientFactory.java | 2 +-
nifi-extension-bundles/nifi-mqtt-bundle/pom.xml | 2 +-
.../nifi-network-processors-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-network-processors/pom.xml | 4 +-
.../nifi/processors/network/ParseNetflowv5.java | 4 +-
.../processors/network/pcap/ByteBufferReader.java | 4 +-
.../apache/nifi/processors/network/pcap/PCAP.java | 3 +-
.../nifi/processors/network/pcap/PCAPHeader.java | 2 +-
.../nifi/processors/network/pcap/Packet.java | 2 +-
.../nifi/processors/network/pcap/SplitPCAP.java | 60 +-
.../nifi/processors/network/pcap/TestPCAP.java | 6 +-
.../processors/network/pcap/TestSplitPCAP.java | 14 +-
.../nifi-network-bundle/nifi-network-utils/pom.xml | 2 +-
.../processors/network/parser/Netflowv5Parser.java | 13 +-
.../processors/network/TestNetflowv5Parser.java | 6 +-
nifi-extension-bundles/nifi-network-bundle/pom.xml | 2 +-
.../nifi-opentelemetry-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-opentelemetry-processors/pom.xml | 10 +-
.../nifi/processors/opentelemetry/ListenOTLP.java | 2 +-
.../server/HttpProtocolNegotiationHandler.java | 2 +-
.../opentelemetry/server/HttpRequestHandler.java | 2 +-
.../opentelemetry/server/HttpServerFactory.java | 4 +-
.../processors/opentelemetry/ListenOTLPTest.java | 15 +-
.../nifi-opentelemetry-bundle/pom.xml | 4 +-
.../nifi-parquet-content-viewer/pom.xml | 12 +-
.../viewer/ParquetServletContextListener.java | 2 +-
.../controller/ParquetContentViewerController.java | 3 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-parquet-bundle/nifi-parquet-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-parquet-processors/pom.xml | 8 +-
.../org/apache/nifi/parquet/ParquetReader.java | 24 +-
.../nifi/parquet/ParquetRecordSetWriter.java | 27 +-
.../nifi/parquet/filter/OffsetRecordFilter.java | 3 +-
.../nifi/parquet/record/ParquetRecordReader.java | 11 +-
.../nifi/parquet/stream/NifiParquetOutputFile.java | 30 +-
.../apache/nifi/parquet/utils/ParquetUtils.java | 9 +-
.../parquet/CalculateParquetOffsets.java | 15 +-
.../parquet/CalculateParquetRowGroupOffsets.java | 13 +-
.../processors/parquet/ConvertAvroToParquet.java | 7 +-
.../nifi/processors/parquet/FetchParquet.java | 5 +-
.../apache/nifi/processors/parquet/PutParquet.java | 5 +-
.../org/apache/nifi/parquet/ParquetTestUtils.java | 18 +-
.../apache/nifi/parquet/TestParquetProcessor.java | 19 +-
.../org/apache/nifi/parquet/TestParquetReader.java | 31 +-
.../parquet/CalculateParquetOffsetsTest.java | 24 +-
.../CalculateParquetRowGroupOffsetsTest.java | 23 +-
.../nifi/processors/parquet/FetchParquetTest.java | 35 +-
.../parquet/TestConvertAvroToParquet.java | 25 +-
.../nifi-parquet-shared/pom.xml | 2 +-
nifi-extension-bundles/nifi-parquet-bundle/pom.xml | 4 +-
.../nifi-pgp-bundle/nifi-pgp-nar/pom.xml | 6 +-
.../nifi-pgp-bundle/nifi-pgp-processors/pom.xml | 6 +-
.../nifi/processors/pgp/DecryptContentPGP.java | 3 +-
.../nifi/processors/pgp/EncryptContentPGP.java | 1 -
.../apache/nifi/processors/pgp/SignContentPGP.java | 2 +-
.../nifi/processors/pgp/VerifyContentPGP.java | 8 +-
.../nifi/processors/pgp/DecryptContentPGPTest.java | 2 +-
.../nifi/processors/pgp/EncryptContentPGPTest.java | 6 +-
.../nifi/processors/pgp/SignContentPGPTest.java | 2 +-
.../nifi/processors/pgp/VerifyContentPGPTest.java | 4 +-
.../nifi-pgp-service-api-nar/pom.xml | 6 +-
.../nifi-pgp-bundle/nifi-pgp-service-api/pom.xml | 2 +-
.../nifi-pgp-bundle/nifi-pgp-service-nar/pom.xml | 6 +-
.../nifi-pgp-bundle/nifi-pgp-service/pom.xml | 6 +-
.../standard/StandardPGPPrivateKeyService.java | 1 -
.../standard/StandardPGPPublicKeyService.java | 1 -
.../standard/StandardPGPPrivateKeyServiceTest.java | 4 +-
.../standard/StandardPGPPublicKeyServiceTest.java | 1 -
.../nifi-pgp-bundle/nifi-pgp-test-utils/pom.xml | 2 +-
nifi-extension-bundles/nifi-pgp-bundle/pom.xml | 2 +-
.../nifi-poi-bundle/nifi-poi-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-poi-bundle/nifi-poi-services/pom.xml | 6 +-
.../apache/nifi/excel/ExcelSchemaInference.java | 2 +-
.../nifi/excel/TestExcelSchemaInference.java | 24 +-
.../excel/TestExcelStartingRowSchemaInference.java | 6 +-
.../nifi/processors/excel/TestSplitExcel.java | 2 +-
nifi-extension-bundles/nifi-poi-bundle/pom.xml | 4 +-
.../nifi-protobuf-services-nar/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-protobuf-services/pom.xml | 8 +-
.../services/protobuf/ProtobufSchemaValidator.java | 2 +-
.../protobuf/converter/ProtobufDataConverter.java | 34 +-
.../protobuf/StandardProtobufReaderTestBase.java | 2 +-
.../nifi/services/protobuf/TestProtobufReader.java | 2 +-
.../protobuf/TestStandardProtobufReader.java | 2 +-
...stStandardProtobufReaderPropertyValidation.java | 2 +-
.../converter/TestProtobufDataConverter.java | 2 +-
.../nifi-protobuf-bundle/pom.xml | 2 +-
.../nifi-py4j-bridge/pom.xml | 8 +-
.../java/org/apache/nifi/py4j/PythonProcess.java | 2 +-
.../apache/nifi/py4j/client/NiFiPythonGateway.java | 1 -
.../nifi/py4j/logback/LevelChangeListener.java | 2 +-
.../apache/nifi/py4j/server/NiFiGatewayServer.java | 2 +-
.../python/processor/PythonProcessorProxy.java | 2 +-
.../python/processor/StandardInputFlowFile.java | 2 +-
.../nifi/py4j/logback/LevelChangeListenerTest.java | 5 +-
.../nifi-py4j-integration-tests/pom.xml | 12 +-
.../PythonControllerInteractionIT.java | 3 +-
.../src/test/resources/logback-test.xml | 3 +
.../nifi-py4j-nar/pom.xml | 6 +-
.../nifi-python-test-extensions/pom.xml | 2 +-
.../nifi-py4j-extension-bundle/pom.xml | 2 +-
.../nifi-redis-extensions/pom.xml | 8 +-
.../redis/service/RedisConnectionPoolService.java | 5 +-
.../org/apache/nifi/redis/state/RedisStateMap.java | 3 +-
.../nifi/redis/state/RedisStateProvider.java | 2 +-
.../redis/processor/TestPutRedisHashRecord.java | 3 +-
.../service/TestRedisConnectionPoolService.java | 2 +-
.../nifi/redis/state/ITRedisStateProvider.java | 5 +-
.../nifi/redis/state/TestRedisStateProvider.java | 2 +-
.../nifi/redis/testcontainers/RedisContainer.java | 4 +-
.../testcontainers/RedisReplicaContainer.java | 2 +-
.../testcontainers/RedisSentinelContainer.java | 4 +-
.../nifi-redis-bundle/nifi-redis-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-redis-service-api-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-redis-service-api/pom.xml | 2 +-
.../nifi-redis-bundle/nifi-redis-utils/pom.xml | 4 +-
.../org/apache/nifi/redis/util/RedisUtils.java | 13 +-
nifi-extension-bundles/nifi-redis-bundle/pom.xml | 6 +-
.../nifi-registry-bundle/nifi-registry-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-registry-service/pom.xml | 8 +-
.../services/StandardJsonSchemaRegistry.java | 6 +-
.../services/TestStandardJsonSchemaRegistry.java | 2 +-
.../nifi-registry-bundle/pom.xml | 2 +-
.../nifi-salesforce-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-salesforce-processors/pom.xml | 8 +-
.../processors/salesforce/PutSalesforceObject.java | 4 +-
.../salesforce/QuerySalesforceObject.java | 19 +-
.../salesforce/util/IncrementalContext.java | 10 +-
.../salesforce/QuerySalesforceObjectTest.java | 131 +
.../src/test/resources/query/describe_sobject.json | 316 +
.../src/test/resources/query/query_sobject.json | 14 +
.../nifi-salesforce-bundle/pom.xml | 2 +-
.../nifi-scripting-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-scripting-processors/pom.xml | 6 +-
.../lookup/script/BaseScriptedLookupService.java | 8 +-
.../nifi/processors/script/ExecuteScript.java | 13 +-
.../processors/script/InvokeScriptedProcessor.java | 16 +-
.../nifi/processors/script/ScriptRunner.java | 1 -
.../processors/script/ScriptedPartitionRecord.java | 4 +-
.../processors/script/ScriptedRecordProcessor.java | 23 +-
.../processors/script/ScriptedRouterProcessor.java | 4 +-
.../processors/script/ScriptedTransformRecord.java | 4 +-
.../script/engine/ClojureScriptEngine.java | 6 +-
.../script/engine/ClojureScriptEngineFactory.java | 4 +-
.../apache/nifi/record/script/ScriptedReader.java | 8 +-
.../record/script/ScriptedRecordSetWriter.java | 8 +-
.../record/sink/script/ScriptedRecordSink.java | 8 +-
.../reporting/script/ScriptedReportingTask.java | 16 +-
.../script/AbstractScriptedControllerService.java | 8 +-
.../apache/nifi/script/ScriptRunnerFactory.java | 8 +-
.../nifi/script/ScriptingComponentHelper.java | 51 +-
.../nifi/script/ScriptingComponentUtils.java | 4 +-
...FilteredPropertiesValidationContextAdapter.java | 144 +-
.../script/TestSimpleScriptedLookupService.java | 1 +
.../processors/script/ExecuteScriptGroovyTest.java | 47 +-
.../nifi/processors/script/TestInvokeGroovy.java | 1 -
.../script/TestScriptingComponentHelper.java | 53 +-
.../nifi/record/script/ScriptedReaderTest.java | 44 +
.../record/script/ScriptedRecordSetWriterTest.java | 8 +-
.../record/sink/script/ScriptedRecordSinkTest.java | 2 +-
.../script/ScriptedReportingTaskTest.java | 56 +-
.../nifi-scripting-bundle/pom.xml | 8 +-
.../nifi-shopify-bundle/nifi-shopify-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-shopify-processors/pom.xml | 4 +-
.../processors/shopify/model/ResourceType.java | 259 +-
.../shopify/util/TestIncrementalTimers.java | 1 -
nifi-extension-bundles/nifi-shopify-bundle/pom.xml | 2 +-
.../nifi-site-to-site-reporting-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-site-to-site-reporting-task/pom.xml | 12 +-
.../reporting/AbstractSiteToSiteReportingTask.java | 6 +-
.../reporting/SiteToSiteBulletinReportingTask.java | 12 +-
.../reporting/SiteToSiteMetricsReportingTask.java | 31 +-
.../SiteToSiteProvenanceReportingTask.java | 35 +-
.../reporting/SiteToSiteStatusReportingTask.java | 31 +-
.../apache/nifi/reporting/s2s/SiteToSiteUtils.java | 7 +-
.../sink/SiteToSiteReportingRecordSink.java | 9 +-
.../TestSiteToSiteBulletinReportingTask.java | 25 +-
.../TestSiteToSiteMetricsReportingTask.java | 38 +-
.../TestSiteToSiteProvenanceReportingTask.java | 23 +-
.../TestSiteToSiteStatusReportingTask.java | 83 +-
.../sink/TestSiteToSiteReportingRecordSink.java | 3 +-
.../nifi-site-to-site-reporting-bundle/pom.xml | 2 +-
.../nifi-slack-bundle/nifi-slack-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-slack-processors/pom.xml | 4 +-
.../apache/nifi/processors/slack/ListenSlack.java | 1 -
.../apache/nifi/processors/slack/PublishSlack.java | 2 +-
.../processors/slack/consume/ConsumeChannel.java | 21 +-
.../slack/consume/ConsumeSlackClient.java | 1 -
.../processors/slack/consume/SlackTimestamp.java | 1 -
.../processors/slack/util/SlackResponseUtil.java | 2 +-
nifi-extension-bundles/nifi-slack-bundle/pom.xml | 2 +-
.../nifi-smb-client-api-nar/pom.xml | 6 +-
.../nifi-smb-bundle/nifi-smb-client-api/pom.xml | 4 +-
.../services/smb/SmbClientProviderService.java | 5 +-
.../nifi/services/smb/SmbListableEntity.java | 11 +-
.../nifi-smb-bundle/nifi-smb-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-smb-bundle/nifi-smb-processors/pom.xml | 8 +-
.../org/apache/nifi/processors/smb/GetSmbFile.java | 274 +-
.../org/apache/nifi/processors/smb/ListSmb.java | 75 +-
.../org/apache/nifi/processors/smb/PutSmbFile.java | 178 +-
.../org/apache/nifi/processors/smb/FetchSmbIT.java | 22 +-
.../apache/nifi/processors/smb/FetchSmbTest.java | 13 +-
.../apache/nifi/processors/smb/GetSmbFileTest.java | 23 +-
.../org/apache/nifi/processors/smb/ListSmbIT.java | 36 +-
.../apache/nifi/processors/smb/ListSmbTest.java | 45 +-
.../apache/nifi/processors/smb/PutSmbFileTest.java | 56 +-
.../nifi/processors/smb/SambaTestContainers.java | 22 +-
.../org/apache/nifi/processors/smb/SmbDfsIT.java | 2 +-
.../nifi-smb-smbj-client-nar/pom.xml | 6 +-
.../nifi-smb-bundle/nifi-smb-smbj-client/pom.xml | 6 +-
.../nifi/services/smb/SmbjClientService.java | 8 +-
.../nifi/services/smb/SmbjClientServiceIT.java | 50 +-
.../nifi/services/smb/SmbjClientServiceTest.java | 2 +-
.../nifi-smb-bundle/nifi-smb-smbj-common/pom.xml | 2 +-
nifi-extension-bundles/nifi-smb-bundle/pom.xml | 2 +-
.../nifi-snmp-bundle/nifi-snmp-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-snmp-bundle/nifi-snmp-processors/pom.xml | 4 +-
.../nifi/snmp/factory/trap/V2TrapPDUFactory.java | 2 +-
.../nifi/snmp/operations/SendTrapSNMPHandler.java | 2 +-
.../processors/properties/V1TrapProperties.java | 1 -
.../java/org/apache/nifi/snmp/utils/SNMPUtils.java | 34 +-
nifi-extension-bundles/nifi-snmp-bundle/pom.xml | 2 +-
.../nifi-snowflake-processors-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-snowflake-processors/pom.xml | 10 +-
.../util/SnowflakeInternalStageTypeParameters.java | 2 +-
.../nifi-snowflake-services-api-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-snowflake-services-api/pom.xml | 2 +-
.../nifi-snowflake-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-snowflake-services/pom.xml | 6 +-
.../nifi-snowflake-bundle/pom.xml | 12 +-
.../nifi-social-media-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-twitter-processors/pom.xml | 2 +-
.../nifi/processors/twitter/ConsumeTwitter.java | 4 +-
.../nifi/processors/twitter/StreamEndpoint.java | 2 +-
.../nifi-social-media-bundle/pom.xml | 2 +-
.../nifi-splunk-bundle/nifi-splunk-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-splunk-processors/pom.xml | 6 +-
.../apache/nifi/processors/splunk/GetSplunk.java | 9 +-
.../apache/nifi/processors/splunk/PutSplunk.java | 24 +-
.../splunk/QuerySplunkIndexingStatus.java | 3 +-
.../nifi/processors/splunk/TestPutSplunk.java | 7 +-
.../nifi/processors/splunk/TestPutSplunkHTTP.java | 2 +-
nifi-extension-bundles/nifi-splunk-bundle/pom.xml | 2 +-
.../nifi-sql-reporting-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-sql-reporting-tasks/pom.xml | 10 +-
.../nifi/reporting/sql/MetricsQueryService.java | 1 -
.../datasources/ProcessGroupStatusDataSource.java | 2 +-
.../sql/datasources/ProcessorStatusDataSource.java | 2 +-
.../reporting/sql/TestQueryNiFiReportingTask.java | 3 +-
.../nifi-sql-reporting-bundle/pom.xml | 4 +-
.../nifi-standard-content-viewer-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-standard-content-viewer/pom.xml | 10 +-
.../StandardContentViewerController.java | 69 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../StandardContentViewerControllerTest.java | 153 +
.../nifi-standard-bundle/nifi-standard-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-standard-parameter-providers/pom.xml | 4 +-
.../nifi/parameter/DatabaseParameterProvider.java | 2 +-
.../EnvironmentVariableParameterProvider.java | 4 +-
.../parameter/OnePasswordParameterProvider.java | 7 +-
.../parameter/TestDatabaseParameterProvider.java | 5 +-
.../TestEnvironmentVariableParameterProvider.java | 3 +-
.../TestOnePasswordParameterProvider.java | 5 +-
.../nifi-standard-processors/pom.xml | 76 +-
.../standard/AbstractDatabaseFetchProcessor.java | 14 +-
.../processors/standard/AbstractExecuteSQL.java | 1 -
.../standard/AbstractQueryDatabaseTable.java | 2 +-
.../nifi/processors/standard/AttributesToCSV.java | 14 +-
.../nifi/processors/standard/AttributesToJSON.java | 4 +-
.../nifi/processors/standard/ControlRate.java | 3 +-
.../processors/standard/DeduplicateRecord.java | 2 +-
.../nifi/processors/standard/DeleteFile.java | 4 +-
.../nifi/processors/standard/DeleteSFTP.java | 89 +-
.../nifi/processors/standard/DistributeLoad.java | 4 +-
.../processors/standard/DuplicateFlowFile.java | 24 +-
.../nifi/processors/standard/EncodeContent.java | 4 +-
.../nifi/processors/standard/EvaluateJsonPath.java | 4 +-
.../nifi/processors/standard/EvaluateXPath.java | 14 +-
.../nifi/processors/standard/EvaluateXQuery.java | 4 +-
.../nifi/processors/standard/ExecuteProcess.java | 78 +-
.../nifi/processors/standard/ExecuteSQLRecord.java | 1 -
.../processors/standard/ExecuteStreamCommand.java | 8 +-
.../nifi/processors/standard/ExtractGrok.java | 4 +-
.../processors/standard/ExtractRecordSchema.java | 2 +-
.../apache/nifi/processors/standard/FetchFTP.java | 1 -
.../nifi/processors/standard/FilterAttribute.java | 6 +-
.../nifi/processors/standard/FlattenJson.java | 2 +-
.../nifi/processors/standard/ForkRecord.java | 246 +-
.../nifi/processors/standard/GenerateFlowFile.java | 2 +-
.../processors/standard/GenerateTableFetch.java | 26 +-
.../apache/nifi/processors/standard/GetFTP.java | 1 -
.../apache/nifi/processors/standard/GetFile.java | 26 +-
.../nifi/processors/standard/GetFileResource.java | 4 +-
.../processors/standard/HandleHttpRequest.java | 338 +-
.../nifi/processors/standard/IdentifyMimeType.java | 1 -
.../nifi/processors/standard/InvokeHTTP.java | 29 +-
.../processors/standard/ListDatabaseTables.java | 4 +-
.../nifi/processors/standard/ListenHTTP.java | 10 +-
.../nifi/processors/standard/ListenSyslog.java | 10 +-
.../apache/nifi/processors/standard/ListenTCP.java | 2 +-
.../apache/nifi/processors/standard/ListenUDP.java | 11 +-
.../nifi/processors/standard/ListenUDPRecord.java | 1 -
.../nifi/processors/standard/LogAttribute.java | 2 -
.../nifi/processors/standard/LookupRecord.java | 1 -
.../nifi/processors/standard/MergeContent.java | 7 +-
.../nifi/processors/standard/MergeRecord.java | 1 -
.../nifi/processors/standard/MonitorActivity.java | 4 +-
.../nifi/processors/standard/PackageFlowFile.java | 1 -
.../nifi/processors/standard/ParseSyslog.java | 1 -
.../nifi/processors/standard/ParseSyslog5424.java | 2 +-
.../nifi/processors/standard/PartitionRecord.java | 4 +-
.../processors/standard/PutDatabaseRecord.java | 94 +-
.../apache/nifi/processors/standard/PutEmail.java | 18 +-
.../apache/nifi/processors/standard/PutFTP.java | 1 -
.../apache/nifi/processors/standard/PutSQL.java | 3 +-
.../processors/standard/QueryDatabaseTable.java | 1 -
.../standard/QueryDatabaseTableRecord.java | 1 -
.../nifi/processors/standard/QueryRecord.java | 2 +-
.../processors/standard/RemoveRecordField.java | 1 -
.../processors/standard/RenameRecordField.java | 5 +
.../nifi/processors/standard/ReplaceText.java | 2 +-
.../nifi/processors/standard/RetryFlowFile.java | 12 +-
.../nifi/processors/standard/RouteOnAttribute.java | 2 +-
.../apache/nifi/processors/standard/RouteText.java | 8 +-
.../nifi/processors/standard/SampleRecord.java | 3 +-
.../apache/nifi/processors/standard/SplitJson.java | 9 +-
.../nifi/processors/standard/SplitRecord.java | 32 +-
.../apache/nifi/processors/standard/SplitText.java | 7 +-
.../apache/nifi/processors/standard/TailFile.java | 3 +-
.../nifi/processors/standard/TransformXml.java | 29 +-
.../nifi/processors/standard/UnpackContent.java | 50 +-
.../processors/standard/UpdateDatabaseTable.java | 2 +-
.../nifi/processors/standard/ValidateCsv.java | 42 +-
.../nifi/processors/standard/ValidateJson.java | 10 +-
.../nifi/processors/standard/ValidateRecord.java | 1 -
.../nifi/processors/standard/ValidateXml.java | 14 +-
.../processors/standard/WaitNotifyProtocol.java | 6 +-
.../processors/standard/db/NameNormalizer.java | 2 +-
.../nifi/processors/standard/db/TableSchema.java | 3 +-
.../standard/db/TranslationStrategy.java | 2 +-
.../standard/db/impl/Oracle12DatabaseAdapter.java | 2 +-
.../standard/db/impl/OracleDatabaseAdapter.java | 2 +-
.../processors/standard/encoding/EncodingMode.java | 2 +-
.../enrichment/SqlJoinCalciteParameters.java | 2 +-
.../standard/faker/FakerMethodHolder.java | 2 +-
.../processors/standard/ftp/NifiFtpServer.java | 6 +-
.../standard/ftp/StandardFTPClientProvider.java | 19 +-
.../standard/ftp/filesystem/VirtualFtpFile.java | 7 +
.../standard/ftp/filesystem/VirtualPath.java | 5 +
.../nifi/processors/standard/hash/HashService.java | 2 +-
.../http/CertificateAttributesProvider.java | 1 +
.../standard/http/ContentEncodingStrategy.java | 2 +-
...leHttpRequestCertificateAttributesProvider.java | 2 +-
.../standard/merge/AttributeStrategy.java | 4 +-
.../merge/KeepCommonAttributeStrategy.java | 8 +-
.../merge/KeepUniqueAttributeStrategy.java | 8 +-
.../servlets/ContentAcknowledgmentServlet.java | 11 +-
.../standard/servlets/HealthCheckServlet.java | 1 +
.../standard/servlets/ListenHTTPServlet.java | 19 +-
.../standard/socket/ProxySocketFactory.java | 2 +-
.../socket/StandardSocketFactoryProvider.java | 2 +-
.../standard/sql/DefaultAvroSqlWriter.java | 2 +-
.../nifi/processors/standard/sql/SqlWriter.java | 3 +-
.../processors/standard/util/ArgumentUtils.java | 4 +-
.../nifi/processors/standard/util/FTPTransfer.java | 34 +-
.../util/FlowFileAttributesSerializer.java | 10 +-
.../standard/util/ProxyAuthenticator.java | 3 +-
.../processors/standard/util/SFTPTransfer.java | 124 +-
.../SoftLimitBoundedByteArrayOutputStream.java | 2 +-
.../standard/util/ValidatingBase32InputStream.java | 3 +-
.../standard/util/ValidatingBase64InputStream.java | 4 +-
.../src/test/java/TestDynamicEnvironment.java | 2 +-
.../AbstractDatabaseConnectionServiceTest.java | 4 +-
.../nifi/processors/standard/CountTextTest.java | 40 +-
.../standard/CryptographicHashContentTest.java | 22 +-
.../processors/standard/HandleHttpRequestTest.java | 15 +
.../nifi/processors/standard/InvokeHTTPTest.java | 9 +-
.../processors/standard/PutDatabaseRecordTest.java | 177 +-
.../standard/QueryDatabaseTableRecordTest.java | 30 +-
.../standard/QueryDatabaseTableTest.java | 30 +-
.../processors/standard/TestAttributesToCSV.java | 53 +-
.../processors/standard/TestAttributesToJSON.java | 71 +-
.../standard/TestCalculateRecordStats.java | 11 +
.../standard/TestConvertCharacterSet.java | 10 +-
.../processors/standard/TestConvertRecord.java | 3 +-
.../processors/standard/TestDeduplicateRecord.java | 30 +-
.../nifi/processors/standard/TestDeleteFile.java | 2 +-
.../nifi/processors/standard/TestDeleteSFTP.java | 42 +-
.../processors/standard/TestEncodeContent.java | 42 +-
.../nifi/processors/standard/TestEnforceOrder.java | 56 +-
.../processors/standard/TestEvaluateXPath.java | 58 +-
.../processors/standard/TestEvaluateXQuery.java | 174 +-
.../processors/standard/TestExecuteProcess.java | 79 +-
.../nifi/processors/standard/TestExecuteSQL.java | 39 +-
.../processors/standard/TestExecuteSQLRecord.java | 40 +-
.../standard/TestExecuteStreamCommand.java | 870 +-
.../nifi/processors/standard/TestExtractGrok.java | 30 +-
.../standard/TestExtractRecordSchema.java | 31 +-
.../nifi/processors/standard/TestExtractText.java | 7 +-
.../apache/nifi/processors/standard/TestFTP.java | 68 +-
.../standard/TestFetchDistributedMapCache.java | 2 +-
.../nifi/processors/standard/TestFetchFTP.java | 33 +-
.../nifi/processors/standard/TestFetchFile.java | 33 +-
.../processors/standard/TestFilterAttribute.java | 2 +-
.../nifi/processors/standard/TestFlattenJson.java | 557 +-
.../nifi/processors/standard/TestForkRecord.java | 137 +-
.../processors/standard/TestGenerateFlowFile.java | 35 +-
.../processors/standard/TestGenerateRecord.java | 34 +-
.../standard/TestGenerateTableFetch.java | 130 +-
.../processors/standard/TestGetFileResource.java | 2 +-
.../nifi/processors/standard/TestGetSFTP.java | 2 +-
.../standard/TestHandleHttpResponse.java | 10 +-
.../processors/standard/TestIdentifyMimeType.java | 14 +
.../standard/TestListDatabaseTables.java | 25 +-
.../nifi/processors/standard/TestListFile.java | 70 +-
.../nifi/processors/standard/TestListSFTP.java | 20 +-
.../nifi/processors/standard/TestListenHTTP.java | 48 +-
.../nifi/processors/standard/TestListenTCP.java | 25 +-
.../nifi/processors/standard/TestListenUDP.java | 13 +-
.../processors/standard/TestListenUDPRecord.java | 55 +-
.../nifi/processors/standard/TestLogAttribute.java | 43 +-
.../nifi/processors/standard/TestLogMessage.java | 13 +
.../processors/standard/TestLookupAttribute.java | 48 +-
.../nifi/processors/standard/TestLookupRecord.java | 66 +-
.../nifi/processors/standard/TestMergeContent.java | 20 +-
.../nifi/processors/standard/TestMergeRecord.java | 37 +-
.../nifi/processors/standard/TestNotify.java | 22 +-
.../processors/standard/TestPackageFlowFile.java | 11 +
.../processors/standard/TestParseSyslog5424.java | 34 +-
.../processors/standard/TestPartitionRecord.java | 21 +-
.../standard/TestPutDistributedMapCache.java | 2 +-
.../nifi/processors/standard/TestPutEmail.java | 31 +-
.../nifi/processors/standard/TestPutFile.java | 2 +-
.../nifi/processors/standard/TestPutRecord.java | 22 +-
.../nifi/processors/standard/TestPutSFTP.java | 34 +-
.../nifi/processors/standard/TestPutSQL.java | 187 +-
.../nifi/processors/standard/TestPutTCP.java | 32 +-
.../nifi/processors/standard/TestPutUDP.java | 26 +-
.../nifi/processors/standard/TestQueryRecord.java | 131 +-
.../processors/standard/TestRemoveRecordField.java | 1 -
.../nifi/processors/standard/TestReplaceText.java | 357 +-
.../standard/TestReplaceTextWithMapping.java | 2 +-
.../processors/standard/TestRetryFlowFile.java | 19 +-
.../nifi/processors/standard/TestSampleRecord.java | 153 +-
.../nifi/processors/standard/TestScanContent.java | 55 -
.../nifi/processors/standard/TestSplitText.java | 2 +-
.../nifi/processors/standard/TestSplitXml.java | 4 +-
.../nifi/processors/standard/TestTailFile.java | 23 +-
.../nifi/processors/standard/TestTransformXml.java | 75 +-
.../processors/standard/TestUnpackContent.java | 17 +-
.../processors/standard/TestUpdateCounter.java | 46 +-
.../standard/TestUpdateDatabaseTable.java | 281 +-
.../nifi/processors/standard/TestUpdateRecord.java | 273 +-
.../nifi/processors/standard/TestValidateCsv.java | 51 +-
.../processors/standard/TestValidateRecord.java | 36 +-
.../apache/nifi/processors/standard/TestWait.java | 91 +-
.../standard/TestWaitNotifyProtocol.java | 2 +-
.../standard/db/impl/DerbyDatabaseAdapter.java | 187 -
.../standard/db/impl/TestMySQLDatabaseAdapter.java | 2 +-
.../db/impl/TestOracle12DatabaseAdapter.java | 12 +-
.../db/impl/TestOracleDatabaseAdapter.java | 2 +-
.../standard/ftp/TestVirtualFileSystem.java | 1 -
...tpRequestCertificateAttributesProviderTest.java | 4 +-
.../socket/StandardSocketFactoryProviderTest.java | 2 +-
.../nifi/processors/standard/util/JsonUtil.java | 9 +-
.../processors/standard/util/SSHTestServer.java | 2 +-
.../util/TestFlowFileAttributesSerializer.java | 8 +-
...che.nifi.processors.standard.db.DatabaseAdapter | 15 -
.../input/complex-input-json-for-inference.json | 116 +
...ress.json => extract-address-with-parents.json} | 76 +-
.../output/extract-address-without-parents.json | 21 +
.../output/extract-bank-accounts-with-parents.json | 108 +
.../test/resources/TestInvokeHttp/realm.properties | 2 +-
.../input/complex-person.json | 13 +-
.../TestRenameRecordField/input/simple-person.json | 6 +-
.../output/testNestedPath.json | 11 +
.../testRecursivelyReferencingAllFields.json | 11 +
.../testRecursivelyReferencingFieldName.json | 11 +
.../output/testReferencingFieldName.json | 4 +
.../output/testRenameArray.json | 11 +
.../output/testRenameFieldStaticValue.json | 4 +
.../output/testRenameFieldUsingAttribute.json | 4 +
.../output/testRenameMultipleFields.json | 4 +
.../nifi-standard-reporting-tasks/pom.xml | 2 +-
.../org/apache/nifi/controller/MonitorMemory.java | 2 +-
.../nifi-standard-rules/pom.xml | 2 +-
.../flowanalysis/rules/DisallowComponentType.java | 2 +-
.../rules/RequireServerSSLContextService.java | 1 -
.../rules/RestrictBackpressureSettings.java | 2 +-
.../rules/RequireServerSSLContextServiceTest.java | 2 +-
.../rules/RestrictBackpressureSettingsTest.java | 18 +-
.../nifi-standard-bundle/pom.xml | 30 +-
.../nifi-standard-services-api-bom/pom.xml | 46 +-
.../nifi-database-dialect-service-api/pom.xml | 2 +-
.../nifi-database-dialect-service-nar/pom.xml | 6 +-
.../nifi-database-dialect-service/pom.xml | 2 +-
.../service/StandardDatabaseDialectService.java | 6 +-
.../nifi-database-dialect-service-bundle/pom.xml | 2 +-
.../nifi-db-schema-registry-service-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-db-schema-registry-service/pom.xml | 29 +-
.../DatabaseTableSchemaRegistry.java | 6 +-
.../DatabaseTableSchemaRegistryTest.java | 150 +-
.../nifi-db-schema-registry-bundle/pom.xml | 2 +-
.../nifi-dbcp-service-api/pom.xml | 2 +-
.../java/org/apache/nifi/dbcp/DBCPService.java | 8 +-
.../nifi-dbcp-service-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-dbcp-service/pom.xml | 35 +-
.../org/apache/nifi/dbcp/DBCPConnectionPool.java | 56 +-
.../java/org/apache/nifi/dbcp/DBCPServiceTest.java | 95 +-
.../record/sink/db/DatabaseRecordSinkTest.java | 77 +-
.../nifi-hikari-dbcp-service/pom.xml | 11 +-
.../apache/nifi/dbcp/HikariCPConnectionPool.java | 87 +-
.../nifi/dbcp/HikariCPConnectionPoolTest.java | 24 +-
.../nifi-dbcp-service-bundle/pom.xml | 2 +-
.../pom.xml | 2 +-
.../distributed/cache/client/Deserializer.java | 4 +-
.../cache/client/DistributedSetCacheClient.java | 4 +-
.../nifi/distributed/cache/client/Serializer.java | 4 +-
.../nifi-distributed-cache-client-service/pom.xml | 6 +-
.../nifi/distributed/cache/client/CacheClient.java | 2 +-
.../client/CacheClientChannelInitializer.java | 4 +-
.../client/CacheClientChannelPoolFactory.java | 2 +-
.../cache/client/SetCacheClientService.java | 2 +-
.../nifi-distributed-cache-protocol/pom.xml | 2 +-
.../cache/protocol/ProtocolHandshake.java | 6 +-
.../nifi-distributed-cache-server/pom.xml | 14 +-
.../distributed/cache/server/EventCacheServer.java | 2 +-
.../distributed/cache/server/SetCacheServer.java | 5 +-
.../cache/server/map/MapCacheRecord.java | 4 +-
.../cache/server/map/MapCacheServer.java | 9 +-
.../cache/server/map/PersistentMapCache.java | 23 +-
.../cache/server/map/SimpleMapCache.java | 9 +-
.../cache/server/map/StandardMapCacheServer.java | 4 +-
.../cache/server/set/PersistentSetCache.java | 12 +-
.../cache/server/set/SetCacheRecord.java | 4 +-
.../cache/server/set/SimpleSetCache.java | 9 +-
.../cache/server/set/StandardSetCacheServer.java | 2 +-
.../cache/server/map/MapCacheServiceTlsTest.java | 4 +-
.../server/map/StandardMapCacheServerTest.java | 2 +-
.../map/TestDistributedMapServerAndClient.java | 4 +-
.../cache/server/set/SetCacheServiceTest.java | 2 +-
.../set/TestDistributedSetServerAndClient.java | 2 +-
.../nifi-distributed-cache-services-nar/pom.xml | 10 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-distributed-cache-services-bundle/pom.xml | 2 +-
.../nifi-file-resource-service-api/pom.xml | 2 +-
.../nifi-file-resource-service-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-file-resource-service/pom.xml | 2 +-
.../service/StandardFileResourceService.java | 2 +-
.../service/StandardFileResourceServiceTest.java | 2 +-
.../nifi-file-resource-service-bundle/pom.xml | 2 +-
.../nifi-hadoop-dbcp-service-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-hadoop-dbcp-service/pom.xml | 8 +-
.../apache/nifi/dbcp/HadoopDBCPConnectionPool.java | 2 +-
.../org/apache/nifi/dbcp/ValidationResources.java | 2 +-
.../nifi-hadoop-dbcp-service-bundle/pom.xml | 2 +-
.../nifi-http-context-map-api/pom.xml | 2 +-
.../java/org/apache/nifi/http/HttpContextMap.java | 5 +-
.../nifi-http-context-map-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-http-context-map/pom.xml | 2 +-
.../apache/nifi/http/StandardHttpContextMap.java | 19 +-
.../nifi-http-context-map-bundle/pom.xml | 2 +-
.../nifi-kerberos-user-service-api/pom.xml | 2 +-
.../nifi-kerberos-user-service-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-kerberos-user-service/pom.xml | 4 +-
.../nifi-kerberos-user-service-bundle/pom.xml | 2 +-
.../nifi-key-service-api/pom.xml | 2 +-
.../nifi-key-service-nar/pom.xml | 6 +-
.../nifi-key-service/pom.xml | 2 +-
.../key/service/StandardPrivateKeyServiceTest.java | 1 -
.../nifi-key-service-bundle/pom.xml | 2 +-
.../nifi-lookup-service-api/pom.xml | 2 +-
.../apache/nifi/lookup/LookupFailureException.java | 1 -
.../java/org/apache/nifi/lookup/LookupService.java | 4 +-
.../nifi-lookup-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-lookup-services/pom.xml | 12 +-
.../apache/nifi/lookup/CSVRecordLookupService.java | 26 +-
.../lookup/DistributedMapCacheLookupService.java | 26 +-
.../org/apache/nifi/lookup/RestLookupService.java | 10 +-
.../nifi/lookup/SimpleCsvFileLookupService.java | 22 +-
.../apache/nifi/lookup/XMLFileLookupService.java | 1 -
.../configuration2/SafeXMLConfiguration.java | 18 +-
.../nifi/lookup/configuration2/XXEValidator.java | 13 +-
.../nifi/lookup/maxmind/AnonymousIpSchema.java | 4 +-
.../org/apache/nifi/lookup/maxmind/CitySchema.java | 6 +-
.../nifi/lookup/maxmind/ContainerSchema.java | 4 +-
.../nifi/lookup/maxmind/IPLookupService.java | 1 -
.../org/apache/nifi/lookup/maxmind/IspSchema.java | 4 +-
.../nifi/lookup/TestCSVRecordLookupService.java | 5 +-
.../TestDistributedMapCacheLookupService.java | 2 +-
.../lookup/TestPropertiesFileLookupService.java | 2 +-
.../lookup/TestRestLookupServiceMarkReset.java | 2 +-
.../lookup/TestSimpleCsvFileLookupService.java | 2 +-
.../lookup/TestSimpleKeyValueLookupService.java | 2 +-
.../nifi/lookup/TestXMLFileLookupService.java | 2 +-
.../lookup/configuration2/XXEValidatorTest.java | 2 +-
.../nifi-lookup-services-bundle/pom.xml | 2 +-
.../nifi-oauth2-provider-api/pom.xml | 2 +-
.../nifi-oauth2-provider-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-oauth2-provider-service/pom.xml | 2 +-
.../apache/nifi/oauth2/HttpProtocolStrategy.java | 3 +-
.../oauth2/JWTBearerOAuth2AccessTokenProvider.java | 3 +-
.../oauth2/StandardOauth2AccessTokenProvider.java | 5 +-
.../JWTBearerOAuth2AccessTokenProviderTest.java | 3 +-
.../StandardOauth2AccessTokenProviderTest.java | 2 +-
.../nifi-oauth2-provider-bundle/pom.xml | 2 +-
.../nifi-proxy-configuration-api/pom.xml | 2 +-
.../nifi-proxy-configuration-nar/pom.xml | 6 +-
.../nifi-proxy-configuration/pom.xml | 2 +-
.../nifi-proxy-configuration-bundle/pom.xml | 2 +-
.../nifi-record-serialization-service-api/pom.xml | 2 +-
.../nifi/serialization/RecordSetWriterFactory.java | 8 +-
.../nifi-record-serialization-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../pom.xml | 6 +-
.../nifi-record-serialization-services/pom.xml | 18 +-
.../nifi/avro/AvroReaderWithExplicitSchema.java | 14 +-
.../avro/EmbeddedAvroSchemaAccessStrategy.java | 12 +-
.../apache/nifi/avro/NonCachingDatumReader.java | 1 -
.../nifi/avro/WriteAvroResultWithSchema.java | 10 +-
.../java/org/apache/nifi/cef/ValidateLocale.java | 2 +-
.../apache/nifi/csv/AbstractCSVRecordReader.java | 3 +-
.../apache/nifi/csv/CSVHeaderSchemaStrategy.java | 17 +-
.../java/org/apache/nifi/csv/CSVRecordReader.java | 23 +-
.../java/org/apache/nifi/csv/CSVRecordSource.java | 15 +-
.../apache/nifi/csv/JacksonCSVRecordReader.java | 34 +-
.../java/org/apache/nifi/csv/WriteCSVResult.java | 15 +-
.../org/apache/nifi/csv/WriteFastCSVResult.java | 6 +-
.../main/java/org/apache/nifi/grok/GrokReader.java | 2 +-
.../org/apache/nifi/grok/GrokRecordReader.java | 25 +-
.../java/org/apache/nifi/json/JsonPathReader.java | 2 +-
.../org/apache/nifi/json/JsonPathValidator.java | 3 +-
.../java/org/apache/nifi/lookup/ReaderLookup.java | 2 +-
.../apache/nifi/lookup/RecordSetWriterLookup.java | 2 +-
.../serialization/DateTimeTextRecordSetWriter.java | 8 +-
.../java/org/apache/nifi/syslog/SyslogReader.java | 2 +-
.../org/apache/nifi/text/FreeFormTextWriter.java | 14 +-
.../nifi/windowsevent/WindowsEventLogReader.java | 1 -
.../windowsevent/WindowsEventLogRecordReader.java | 16 +-
.../java/org/apache/nifi/xml/WriteXMLResult.java | 7 +-
.../main/java/org/apache/nifi/xml/XMLReader.java | 430 +-
.../java/org/apache/nifi/xml/XMLRecordReader.java | 1048 +--
.../apache/nifi/xml/inference/XmlRecordSource.java | 12 +-
.../avro/TestAvroReaderWithEmbeddedSchema.java | 4 +-
.../avro/TestAvroReaderWithExplicitSchema.java | 27 +-
.../org/apache/nifi/avro/TestWriteAvroResult.java | 14 +-
.../org/apache/nifi/cef/TestCEFRecordReader.java | 4 +-
.../apache/nifi/csv/ITApacheCSVRecordReader.java | 13 +-
.../apache/nifi/csv/ITJacksonCSVRecordReader.java | 11 +-
.../org/apache/nifi/csv/TestCSVRecordReader.java | 25 +-
.../org/apache/nifi/csv/TestCSVValidators.java | 1 -
.../apache/nifi/csv/TestFastCSVRecordReader.java | 15 +-
.../nifi/csv/TestJacksonCSVRecordReader.java | 15 +-
.../org/apache/nifi/csv/TestWriteCSVResult.java | 26 +-
.../apache/nifi/csv/TestWriteFastCSVResult.java | 27 +-
.../org/apache/nifi/grok/TestGrokRecordReader.java | 2 +-
.../json/TestInferJsonSchemaAccessStrategy.java | 6 +-
.../apache/nifi/json/TestJsonSchemaInference.java | 4 +-
.../nifi/json/TestJsonTreeRowRecordReader.java | 10 +-
.../org/apache/nifi/json/TestWriteJsonResult.java | 2 +-
.../org/apache/nifi/lookup/TestReaderLookup.java | 2 +-
.../nifi/lookup/TestRecordSetWriterLookup.java | 2 +-
.../serialization/TestRecordReaderProcessor.java | 2 +-
.../TestRecordSetWriterProcessor.java | 2 +-
.../nifi/syslog/TestSyslog5424RecordReader.java | 2 +-
.../nifi/text/TestFreeFormTextRecordSetWriter.java | 2 +-
.../apache/nifi/util/TestSchemaInferenceUtil.java | 2 +-
.../TestWindowsEventLogRecordReader.java | 2 +-
.../org/apache/nifi/xml/TestInferXmlSchema.java | 2 +-
.../org/apache/nifi/xml/TestWriteXMLResult.java | 116 +-
.../apache/nifi/xml/TestXMLRecordSetWriter.java | 2 +-
.../nifi/yaml/TestYamlTreeRowRecordReader.java | 46 +-
.../pom.xml | 2 +-
.../nifi-record-sink-api/pom.xml | 2 +-
.../nifi/record/sink/RetryableIOException.java | 1 -
.../nifi-record-sink-service-nar/pom.xml | 6 +-
.../nifi-record-sink-service/pom.xml | 10 +-
.../apache/nifi/record/sink/HttpRecordSink.java | 4 +-
.../sink/lookup/RecordSinkServiceLookup.java | 1 -
.../nifi/record/sink/TestHttpRecordSink.java | 14 +-
.../nifi/record/sink/TestLoggingRecordSink.java | 3 +-
.../org/apache/nifi/record/sink/TestProcessor.java | 10 +-
.../sink/lookup/TestRecordSinkServiceLookup.java | 6 +-
.../nifi-record-sink-service-bundle/pom.xml | 2 +-
.../nifi-schema-registry-service-api/pom.xml | 2 +-
.../services/JsonSchemaRegistry.java | 3 +-
.../services/MessageNameResolver.java | 1 +
.../services/StandardSchemaDefinition.java | 2 +-
.../nifi-ssl-context-service-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-ssl-context-service/pom.xml | 6 +-
.../nifi/ssl/PEMEncodedSSLContextProvider.java | 18 +-
.../ssl/StandardRestrictedSSLContextService.java | 5 +-
.../apache/nifi/ssl/StandardSSLContextService.java | 12 +-
.../apache/nifi/ssl/StandardTlsConfiguration.java | 8 +-
.../nifi/ssl/PEMEncodedSSLContextProviderTest.java | 13 +-
.../StandardRestrictedSSLContextServiceTest.java | 5 +-
.../nifi/ssl/StandardSSLContextServiceTest.java | 8 +-
.../nifi-ssl-context-bundle/pom.xml | 2 +-
.../nifi-ssl-context-service-api/pom.xml | 2 +-
.../org/apache/nifi/ssl/SSLContextProvider.java | 2 +-
.../nifi-standard-services-api-nar/pom.xml | 2 +-
.../nifi-web-client-provider-api/pom.xml | 2 +-
.../nifi-web-client-provider-service-nar/pom.xml | 6 +-
.../nifi-web-client-provider-service/pom.xml | 8 +-
.../service/StandardWebClientServiceProvider.java | 9 +-
.../StandardWebClientServiceProviderTest.java | 9 +-
.../nifi-web-client-provider-bundle/pom.xml | 2 +-
.../nifi-standard-services/pom.xml | 2 +-
.../nifi-standard-shared-bom/pom.xml | 6 +-
.../nifi-standard-shared-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-standard-shared-bundle/pom.xml | 2 +-
.../nifi-stateful-analysis-nar/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-stateful-analysis-processors/pom.xml | 4 +-
.../analysis/AttributeRollingWindowIT.java | 9 +-
.../nifi-stateful-analysis-bundle/pom.xml | 4 +-
.../nifi-update-attribute-model/pom.xml | 4 +-
.../org/apache/nifi/update/attributes/Rule.java | 3 +-
.../update/attributes/serde/CriteriaSerDe.java | 14 +-
.../nifi-update-attribute-nar/pom.xml | 12 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-update-attribute-processor/pom.xml | 4 +-
.../processors/attributes/UpdateAttribute.java | 97 +-
.../update/attributes/TestUpdateAttribute.java | 2 +-
.../nifi-update-attribute-ui/pom.xml | 14 +-
.../attributes/UpdateAttributeModelFactory.java | 2 +-
.../nifi/update/attributes/api/RuleResource.java | 42 +-
.../nifi/update/attributes/dto/ActionDTO.java | 4 +-
.../nifi/update/attributes/dto/ConditionDTO.java | 4 +-
.../nifi/update/attributes/dto/DtoFactory.java | 7 +-
.../apache/nifi/update/attributes/dto/RuleDTO.java | 3 +-
.../attributes/entity/EvaluationContextEntity.java | 1 +
.../nifi/update/attributes/entity/RuleEntity.java | 3 +-
.../nifi/update/attributes/entity/RulesEntity.java | 3 +-
.../attributes/mapper/ObjectMapperResolver.java | 1 -
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-update-attribute-bundle/pom.xml | 2 +-
.../nifi-websocket-processors-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-websocket-processors/pom.xml | 6 +-
.../AbstractWebSocketGatewayProcessor.java | 2 +-
.../nifi/processors/websocket/PutWebSocket.java | 46 +-
.../processors/websocket/TestConnectWebSocket.java | 1 -
.../processors/websocket/TestListenWebSocket.java | 1 -
.../processors/websocket/TestPutWebSocket.java | 1 -
.../nifi-websocket-services-api-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-websocket-services-api/pom.xml | 2 +-
.../nifi/websocket/AbstractWebSocketService.java | 2 +-
.../nifi/websocket/WebSocketMessageRouter.java | 8 +-
.../nifi/websocket/TestWebSocketMessageRouter.java | 1 -
.../websocket/TestWebSocketMessageRouters.java | 1 -
.../nifi-websocket-services-jetty-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-websocket-services-jetty/pom.xml | 8 +-
.../nifi/websocket/jetty/JettyWebSocketClient.java | 3 +-
.../nifi/websocket/jetty/JettyWebSocketServer.java | 2 +-
.../jetty/ControllerServiceTestContext.java | 1 -
.../websocket/jetty/TestJettyWebSocketClient.java | 1 -
.../websocket/util/HeaderMapExtractorTest.java | 134 +-
.../nifi-websocket-bundle/pom.xml | 2 +-
.../nifi-windows-event-log-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-windows-event-log-processors/pom.xml | 4 +-
.../windows/event/log/ConsumeWindowsEventLog.java | 1 -
.../jna/EventSubscribeXmlRenderingCallback.java | 5 +-
.../event/log/ConsumeWindowsEventLogTest.java | 22 +-
.../windows/event/log/JNALauncherInterceptor.java | 14 +-
.../nifi-windows-event-log-bundle/pom.xml | 2 +-
.../nifi-workday-processors-nar/pom.xml | 6 +-
.../nifi-workday-processors/pom.xml | 6 +-
.../processors/workday/GetWorkdayReportTest.java | 2 +-
nifi-extension-bundles/nifi-workday-bundle/pom.xml | 2 +-
.../nifi-zendesk-common/pom.xml | 4 +-
.../JsonPointerPropertyNameValidator.java | 36 +-
.../nifi-zendesk-bundle/nifi-zendesk-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-zendesk-processors/pom.xml | 6 +-
.../nifi-zendesk-services-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-zendesk-services/pom.xml | 6 +-
.../nifi/services/zendesk/ZendeskRecordSink.java | 8 +-
nifi-extension-bundles/nifi-zendesk-bundle/pom.xml | 2 +-
nifi-extension-bundles/pom.xml | 3 +-
nifi-framework-api/pom.xml | 2 +-
.../FlowActionReporterConfigurationContext.java | 2 +-
.../LoginIdentityProviderConfigurationContext.java | 3 +-
.../AbstractPolicyBasedAuthorizer.java | 14 +-
.../nifi/components/state/StateProvider.java | 4 +-
.../state/StateProviderInitializationContext.java | 7 +-
.../NonComponentConfigurationContext.java | 3 +-
.../org/apache/nifi/controller/Triggerable.java | 4 +-
.../controller/repository/ContentRepository.java | 5 +-
.../repository/IncompleteSwapFileException.java | 1 -
.../nifi/controller/repository/QueueProvider.java | 4 +-
.../controller/repository/RepositoryRecord.java | 4 +-
.../nifi/controller/repository/SwapSummary.java | 4 +-
.../status/history/StatusHistoryDump.java | 2 +-
.../java/org/apache/nifi/events/EventReporter.java | 4 +-
.../lineage/ProvenanceEventLineageNode.java | 4 +-
.../org/apache/nifi/provenance/search/Query.java | 1 -
.../apache/nifi/provenance/search/QueryResult.java | 4 +-
.../nifi/web/NiFiWebConfigurationContext.java | 4 +-
.../main/java/org/apache/nifi/web/Revision.java | 2 +-
.../java/org/apache/nifi/web/TestRevision.java | 6 +-
.../pom.xml | 6 +-
.../nifi-framework-kubernetes-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../pom.xml | 6 +-
.../provider/KubernetesConfigMapStateProvider.java | 125 +-
.../state/provider/StandardStateMap.java | 13 +-
.../KubernetesConfigMapStateProviderTest.java | 7 +-
.../nifi-framework-kubernetes-bundle/pom.xml | 2 +-
.../nifi-framework-cluster-zookeeper/pom.xml | 4 +-
.../cluster/zookeeper/ZooKeeperClientConfig.java | 8 +-
.../pom.xml | 8 +-
.../zookeeper/CuratorACLProviderFactory.java | 8 +-
.../nifi-framework-zookeeper-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../pom.xml | 4 +-
.../zookeeper/ZooKeeperStateProvider.java | 6 +-
.../nifi-framework-zookeeper-bundle/pom.xml | 2 +-
.../nifi-kerberos-iaa-providers-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-kerberos-iaa-providers/pom.xml | 6 +-
.../kerberos/parser/KerberosPrincipalParser.java | 2 +-
.../nifi-kerberos-iaa-providers-bundle/pom.xml | 2 +-
.../nifi-ldap-iaa-providers-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-ldap-iaa-providers/pom.xml | 12 +-
.../java/org/apache/nifi/ldap/LdapProvider.java | 13 +-
.../org/apache/nifi/ldap/LdapsSocketFactory.java | 4 +-
.../nifi/ldap/ssl/LdapSslContextProvider.java | 2 +-
.../ldap/ssl/StandardLdapSslContextProvider.java | 2 +-
.../nifi/ldap/tenants/LdapUserGroupProvider.java | 14 +-
.../org/apache/nifi/ldap/tenants/TenantHolder.java | 1 -
.../ssl/StandardLdapSslContextProviderTest.java | 4 +-
.../ldap/tenants/LdapUserGroupProviderTest.java | 142 +-
.../nifi-ldap-iaa-providers-bundle/pom.xml | 2 +-
.../nifi-persistent-provenance-repository/pom.xml | 8 +-
.../nifi/provenance/AbstractRecordWriter.java | 10 +-
.../provenance/ByteArraySchemaRecordReader.java | 9 +-
.../provenance/ByteArraySchemaRecordWriter.java | 14 +-
.../provenance/EventIdFirstSchemaRecordReader.java | 13 +-
.../nifi/provenance/RepositoryConfiguration.java | 13 +-
.../provenance/WriteAheadProvenanceRepository.java | 3 +-
.../provenance/authorization/EventAuthorizer.java | 8 +-
.../provenance/authorization/EventTransformer.java | 4 +-
.../nifi/provenance/index/EventIndexSearcher.java | 4 +-
.../nifi/provenance/index/EventIndexWriter.java | 6 +-
.../index/lucene/IndexDirectoryManager.java | 4 +-
.../provenance/index/lucene/IndexableDocument.java | 4 +-
.../nifi/provenance/index/lucene/QueryTask.java | 4 +-
.../apache/nifi/provenance/lucene/DocsReader.java | 36 +-
.../lucene/DocumentToEventConverter.java | 6 +-
.../nifi/provenance/lucene/IndexManager.java | 8 +-
.../nifi/provenance/lucene/IndexingAction.java | 2 +-
.../lucene/LuceneEventIndexSearcher.java | 6 +-
.../provenance/lucene/LuceneEventIndexWriter.java | 8 +-
.../apache/nifi/provenance/lucene/LuceneUtil.java | 4 +-
.../provenance/lucene/UpdateMinimumEventId.java | 6 +-
.../schema/EventIdFirstHeaderSchema.java | 6 +-
.../apache/nifi/provenance/schema/EventRecord.java | 8 +-
.../nifi/provenance/schema/EventRecordFields.java | 8 +-
.../provenance/schema/LookupTableEventRecord.java | 16 +-
.../schema/LookupTableEventRecordFields.java | 8 +-
.../provenance/schema/LookupTableEventSchema.java | 13 +-
.../provenance/schema/ProvenanceEventSchema.java | 12 +-
.../serialization/CompressableRecordReader.java | 17 +-
.../serialization/EmptyRecordReader.java | 6 +-
.../provenance/serialization/RecordReader.java | 8 +-
.../provenance/serialization/RecordWriters.java | 8 +-
.../nifi/provenance/store/EventFileManager.java | 6 +-
.../nifi/provenance/store/EventStorePartition.java | 8 +-
.../provenance/store/PartitionedEventStore.java | 2 +-
.../nifi/provenance/store/RecordReaderFactory.java | 4 +-
.../nifi/provenance/store/RecordWriterFactory.java | 4 +-
.../nifi/provenance/store/StorageResult.java | 6 +-
.../store/iterator/AuthorizingEventIterator.java | 6 +-
.../SelectiveRecordReaderEventIterator.java | 14 +-
.../SequentialRecordReaderEventIterator.java | 8 +-
.../nifi/provenance/toc/StandardTocReader.java | 18 +-
.../nifi/provenance/toc/StandardTocWriter.java | 12 +-
.../org/apache/nifi/provenance/toc/TocUtil.java | 4 +-
.../apache/nifi/provenance/util/CloseableUtil.java | 4 +-
.../apache/nifi/provenance/util/DumpEventFile.java | 10 +-
.../nifi/provenance/util/StorageSummaryEvent.java | 5 +-
.../provenance/AbstractTestRecordReaderWriter.java | 11 +-
.../provenance/TestSchemaRecordReaderWriter.java | 46 +-
.../java/org/apache/nifi/provenance/TestUtil.java | 4 +-
.../index/lucene/TestLuceneEventIndex.java | 5 +-
.../provenance/store/TestEventFileManager.java | 6 +-
.../TestSelectiveRecordReaderEventIterator.java | 32 +-
.../nifi/provenance/toc/TestStandardTocReader.java | 8 +-
.../nifi/provenance/toc/TestStandardTocWriter.java | 6 +-
.../nifi-provenance-repository-nar/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-volatile-provenance-repository/pom.xml | 6 +-
.../TestVolatileProvenanceRepository.java | 2 +-
.../nifi-provenance-repository-bundle/pom.xml | 6 +-
.../nifi-python-extension-api/pom.xml | 2 +-
.../nifi-python-framework-api/pom.xml | 2 +-
.../documentation/PropertyDependency.java | 4 +-
.../documentation/PropertyDescription.java | 3 +-
.../nifi-python-framework/pom.xml | 2 +-
.../nifi-py4j-framework-bundle/pom.xml | 2 +-
.../nifi-questdb-status-history-nar/pom.xml | 6 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-questdb-status-history/pom.xml | 8 +-
.../status/history/questdb/CapturedStatus.java | 8 +-
.../questdb/CounterStatisticsResultProcessor.java | 2 +-
.../status/history/questdb/StorageStatusType.java | 2 +-
.../AbstractStatusHistoryRepositoryTest.java | 2 +-
.../nifi-questdb-bundle/nifi-questdb/pom.xml | 6 +-
.../org/apache/nifi/questdb/QueryRowContext.java | 2 +-
.../nifi/questdb/embedded/EmbeddedClient.java | 2 +-
.../questdb/embedded/ManagedTableDefinition.java | 8 +-
.../nifi/questdb/embedded/RetryingClient.java | 2 +-
.../SimpleEmbeddedDatabaseManagerContext.java | 2 +-
.../rollover/DeleteOldRolloverStrategyTest.java | 2 +-
.../java/org/apache/nifi/questdb/util/Event.java | 8 +-
.../nifi-questdb-bundle/pom.xml | 2 +-
.../nifi-single-user-iaa-providers-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-single-user-iaa-providers/pom.xml | 6 +-
.../single/user/SingleUserAuthorizer.java | 8 +-
.../nifi-single-user-iaa-providers-bundle/pom.xml | 2 +-
.../nifi-framework-extensions/pom.xml | 2 +-
.../nifi-framework-nar-bom/pom.xml | 52 +-
nifi-framework-bundle/nifi-framework-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-framework/nifi-administration/pom.xml | 4 +-
.../main/java/org/apache/nifi/history/History.java | 3 +-
.../nifi-framework/nifi-authorizer/pom.xml | 16 +-
.../nifi/authorization/AuthorizerFactoryBean.java | 41 +-
.../nifi/authorization/AuthorizerFactoryTest.java | 2 +-
.../nifi-framework/nifi-client-dto/pom.xml | 4 +-
.../java/org/apache/nifi/web/api/dto/AboutDTO.java | 4 +-
.../apache/nifi/web/api/dto/AccessPolicyDTO.java | 2 +-
.../nifi/web/api/dto/AccessPolicySummaryDTO.java | 3 +-
.../nifi/web/api/dto/AccessTokenExpirationDTO.java | 4 +-
.../nifi/web/api/dto/AffectedComponentDTO.java | 2 +-
.../apache/nifi/web/api/dto/AllowableValueDTO.java | 1 -
.../nifi/web/api/dto/AnalyzeFlowRequestDTO.java | 1 -
.../nifi/web/api/dto/AsynchronousRequestDTO.java | 2 +-
.../org/apache/nifi/web/api/dto/BannerDTO.java | 1 -
.../apache/nifi/web/api/dto/BatchSettingsDTO.java | 1 -
.../apache/nifi/web/api/dto/BulletinBoardDTO.java | 4 +-
.../org/apache/nifi/web/api/dto/BulletinDTO.java | 4 +-
.../apache/nifi/web/api/dto/BulletinQueryDTO.java | 1 -
.../org/apache/nifi/web/api/dto/BundleDTO.java | 2 +-
.../org/apache/nifi/web/api/dto/ClusterDTO.java | 4 +-
.../apache/nifi/web/api/dto/ClusterSummaryDTO.java | 1 -
.../org/apache/nifi/web/api/dto/ComponentDTO.java | 1 -
.../nifi/web/api/dto/ComponentDifferenceDTO.java | 2 +-
.../nifi/web/api/dto/ComponentHistoryDTO.java | 2 +-
.../nifi/web/api/dto/ComponentReferenceDTO.java | 1 -
.../api/dto/ComponentRestrictionPermissionDTO.java | 2 +-
.../apache/nifi/web/api/dto/ComponentStateDTO.java | 1 -
.../web/api/dto/ComponentValidationResultDTO.java | 2 +-
.../nifi/web/api/dto/ConfigurationAnalysisDTO.java | 2 +-
.../api/dto/ConfigurationStepConfigurationDTO.java | 2 +-
.../apache/nifi/web/api/dto/ConnectableDTO.java | 1 -
.../apache/nifi/web/api/dto/ContentViewerDTO.java | 1 +
.../web/api/dto/ControllerConfigurationDTO.java | 1 -
.../org/apache/nifi/web/api/dto/ControllerDTO.java | 2 +-
.../nifi/web/api/dto/ControllerServiceApiDTO.java | 2 +-
.../nifi/web/api/dto/ControllerServiceDTO.java | 2 +-
.../ControllerServiceReferencingComponentDTO.java | 2 +-
.../org/apache/nifi/web/api/dto/CounterDTO.java | 1 -
.../org/apache/nifi/web/api/dto/CountersDTO.java | 2 +-
.../nifi/web/api/dto/CountersSnapshotDTO.java | 4 +-
.../org/apache/nifi/web/api/dto/DifferenceDTO.java | 9 +-
.../org/apache/nifi/web/api/dto/DimensionsDTO.java | 1 -
.../apache/nifi/web/api/dto/DocumentedTypeDTO.java | 2 +-
.../apache/nifi/web/api/dto/DropRequestDTO.java | 4 +-
.../nifi/web/api/dto/ExplicitRestrictionDTO.java | 1 -
.../nifi/web/api/dto/FlowAnalysisRuleDTO.java | 2 +-
.../nifi/web/api/dto/FlowConfigurationDTO.java | 4 +-
.../org/apache/nifi/web/api/dto/FlowFileDTO.java | 2 +-
.../nifi/web/api/dto/FlowFileSummaryDTO.java | 1 -
.../nifi/web/api/dto/FlowRegistryBucketDTO.java | 1 -
.../nifi/web/api/dto/FlowRegistryClientDTO.java | 2 +-
.../apache/nifi/web/api/dto/FlowSnippetDTO.java | 2 +-
.../nifi/web/api/dto/FlowUpdateRequestDTO.java | 2 +-
.../nifi/web/api/dto/JmxMetricsResultDTO.java | 7 +-
.../java/org/apache/nifi/web/api/dto/LabelDTO.java | 2 +-
.../apache/nifi/web/api/dto/ListingRequestDTO.java | 4 +-
.../nifi/web/api/dto/NodeCountersSnapshotDTO.java | 1 -
.../java/org/apache/nifi/web/api/dto/NodeDTO.java | 4 +-
.../org/apache/nifi/web/api/dto/NodeEventDTO.java | 4 +-
.../api/dto/NodeSystemDiagnosticsSnapshotDTO.java | 1 -
.../nifi/web/api/dto/ParameterContextDTO.java | 2 +-
.../web/api/dto/ParameterContextReferenceDTO.java | 1 -
.../api/dto/ParameterContextUpdateRequestDTO.java | 2 +-
.../dto/ParameterContextValidationRequestDTO.java | 3 +-
.../dto/ParameterContextValidationResultsDTO.java | 1 +
.../org/apache/nifi/web/api/dto/ParameterDTO.java | 3 +-
...ParameterProviderApplyParametersRequestDTO.java | 2 +-
.../api/dto/ParameterProviderConfigurationDTO.java | 1 -
.../nifi/web/api/dto/ParameterProviderDTO.java | 4 +-
.../ParameterProviderReferencingComponentDTO.java | 1 -
.../nifi/web/api/dto/ParameterStatusDTO.java | 3 +-
.../apache/nifi/web/api/dto/PermissionsDTO.java | 1 -
.../java/org/apache/nifi/web/api/dto/PortDTO.java | 2 +-
.../org/apache/nifi/web/api/dto/PositionDTO.java | 1 -
.../apache/nifi/web/api/dto/PreviousValueDTO.java | 4 +-
.../apache/nifi/web/api/dto/ProcessGroupDTO.java | 3 +-
.../nifi/web/api/dto/ProcessGroupNameDTO.java | 1 -
.../org/apache/nifi/web/api/dto/ProcessorDTO.java | 6 +-
.../web/api/dto/ProcessorRunStatusDetailsDTO.java | 2 +-
.../nifi/web/api/dto/PropertyDependencyDTO.java | 2 +-
.../nifi/web/api/dto/PropertyDescriptorDTO.java | 2 +-
.../web/api/dto/PropertyGroupConfigurationDTO.java | 2 +-
.../nifi/web/api/dto/PropertyHistoryDTO.java | 2 +-
.../org/apache/nifi/web/api/dto/QueueSizeDTO.java | 1 -
.../apache/nifi/web/api/dto/RelationshipDTO.java | 1 -
.../web/api/dto/RemoteProcessGroupContentsDTO.java | 2 +-
.../nifi/web/api/dto/RemoteProcessGroupDTO.java | 5 +-
.../apache/nifi/web/api/dto/ReportingTaskDTO.java | 2 +-
.../nifi/web/api/dto/RequiredPermissionDTO.java | 2 +-
.../org/apache/nifi/web/api/dto/ResourceDTO.java | 1 -
.../org/apache/nifi/web/api/dto/RevisionDTO.java | 1 -
.../org/apache/nifi/web/api/dto/SnippetDTO.java | 2 +-
.../org/apache/nifi/web/api/dto/StateEntryDTO.java | 1 -
.../org/apache/nifi/web/api/dto/StateMapDTO.java | 2 +-
.../nifi/web/api/dto/SystemDiagnosticsDTO.java | 2 +-
.../web/api/dto/SystemDiagnosticsSnapshotDTO.java | 4 +-
.../org/apache/nifi/web/api/dto/TenantDTO.java | 1 -
.../java/org/apache/nifi/web/api/dto/UserDTO.java | 2 +-
.../org/apache/nifi/web/api/dto/UserGroupDTO.java | 2 +-
.../nifi/web/api/dto/VerifyConfigRequestDTO.java | 2 +-
.../web/api/dto/VersionControlInformationDTO.java | 1 -
.../apache/nifi/web/api/dto/VersionedFlowDTO.java | 1 -
.../web/api/dto/VersionedFlowUpdateRequestDTO.java | 1 -
.../apache/nifi/web/api/dto/action/ActionDTO.java | 4 +-
.../apache/nifi/web/api/dto/action/HistoryDTO.java | 4 +-
.../nifi/web/api/dto/action/HistoryQueryDTO.java | 4 +-
.../component/details/ExtensionDetailsDTO.java | 1 -
.../details/RemoteProcessGroupDetailsDTO.java | 1 -
.../dto/action/details/ConfigureDetailsDTO.java | 1 -
.../api/dto/action/details/ConnectDetailsDTO.java | 1 -
.../web/api/dto/action/details/MoveDetailsDTO.java | 1 -
.../api/dto/action/details/PurgeDetailsDTO.java | 4 +-
.../dto/diagnostics/ClassLoaderDiagnosticsDTO.java | 4 +-
.../dto/diagnostics/ConnectionDiagnosticsDTO.java | 2 +-
.../ConnectionDiagnosticsSnapshotDTO.java | 2 +-
.../ControllerServiceDiagnosticsDTO.java | 4 +-
.../dto/diagnostics/GCDiagnosticsSnapshotDTO.java | 5 +-
.../GarbageCollectionDiagnosticsDTO.java | 7 +-
.../JVMControllerDiagnosticsSnapshotDTO.java | 3 +-
.../web/api/dto/diagnostics/JVMDiagnosticsDTO.java | 5 +-
.../dto/diagnostics/JVMDiagnosticsSnapshotDTO.java | 3 +-
.../diagnostics/JVMFlowDiagnosticsSnapshotDTO.java | 8 +-
.../JVMSystemDiagnosticsSnapshotDTO.java | 7 +-
.../dto/diagnostics/LocalQueuePartitionDTO.java | 1 -
.../diagnostics/NodeGCDiagnosticsSnapshotDTO.java | 3 +-
.../diagnostics/NodeJVMDiagnosticsSnapshotDTO.java | 3 +-
.../dto/diagnostics/ProcessorDiagnosticsDTO.java | 2 +-
.../dto/diagnostics/RemoteQueuePartitionDTO.java | 1 -
.../api/dto/diagnostics/RepositoryUsageDTO.java | 3 +-
.../web/api/dto/diagnostics/ThreadDumpDTO.java | 3 +-
.../nifi/web/api/dto/flow/FlowBreadcrumbDTO.java | 3 +-
.../org/apache/nifi/web/api/dto/flow/FlowDTO.java | 2 +-
.../nifi/web/api/dto/flow/ProcessGroupFlowDTO.java | 4 +-
.../nifi/web/api/dto/provenance/AttributeDTO.java | 1 -
.../nifi/web/api/dto/provenance/ProvenanceDTO.java | 4 +-
.../web/api/dto/provenance/ProvenanceEventDTO.java | 4 +-
.../api/dto/provenance/ProvenanceOptionsDTO.java | 2 +-
.../api/dto/provenance/ProvenanceRequestDTO.java | 4 +-
.../api/dto/provenance/ProvenanceResultsDTO.java | 4 +-
.../dto/provenance/ProvenanceSearchValueDTO.java | 3 +-
.../provenance/ProvenanceSearchableFieldDTO.java | 1 -
.../web/api/dto/provenance/lineage/LineageDTO.java | 4 +-
.../dto/provenance/lineage/LineageRequestDTO.java | 1 -
.../dto/provenance/lineage/LineageResultsDTO.java | 2 +-
.../dto/provenance/lineage/ProvenanceLinkDTO.java | 4 +-
.../dto/provenance/lineage/ProvenanceNodeDTO.java | 4 +-
.../apache/nifi/web/api/dto/remote/PeerDTO.java | 1 -
.../api/dto/search/ComponentSearchResultDTO.java | 2 +-
.../web/api/dto/search/NodeSearchResultDTO.java | 1 -
.../web/api/dto/search/SearchResultGroupDTO.java | 1 -
.../nifi/web/api/dto/search/SearchResultsDTO.java | 2 +-
.../nifi/web/api/dto/status/ClusterStatusDTO.java | 2 +-
.../web/api/dto/status/ComponentStatusDTO.java | 1 -
.../api/dto/status/ConnectionStatisticsDTO.java | 4 +-
.../status/ConnectionStatisticsSnapshotDTO.java | 3 +-
.../web/api/dto/status/ConnectionStatusDTO.java | 4 +-
.../ConnectionStatusPredictionsSnapshotDTO.java | 1 -
.../dto/status/ConnectionStatusSnapshotDTO.java | 1 -
.../api/dto/status/ControllerServiceStatusDTO.java | 1 -
.../web/api/dto/status/ControllerStatusDTO.java | 1 -
.../api/dto/status/FlowAnalysisRuleStatusDTO.java | 1 -
.../NodeConnectionStatisticsSnapshotDTO.java | 1 -
.../status/NodeConnectionStatusSnapshotDTO.java | 1 -
.../nifi/web/api/dto/status/NodePortStatusDTO.java | 3 +-
.../api/dto/status/NodePortStatusSnapshotDTO.java | 1 -
.../dto/status/NodeProcessorStatusSnapshotDTO.java | 1 -
.../NodeRemoteProcessGroupStatusSnapshotDTO.java | 1 -
.../nifi/web/api/dto/status/NodeStatusDTO.java | 3 +-
.../web/api/dto/status/NodeStatusSnapshotsDTO.java | 2 +-
.../nifi/web/api/dto/status/PortStatusDTO.java | 4 +-
.../web/api/dto/status/PortStatusSnapshotDTO.java | 1 -
.../web/api/dto/status/ProcessGroupStatusDTO.java | 4 +-
.../dto/status/ProcessGroupStatusSnapshotDTO.java | 4 +-
.../web/api/dto/status/ProcessorStatusDTO.java | 4 +-
.../api/dto/status/ProcessorStatusSnapshotDTO.java | 1 -
.../dto/status/RemoteProcessGroupStatusDTO.java | 4 +-
.../RemoteProcessGroupStatusSnapshotDTO.java | 1 -
.../web/api/dto/status/ReportingTaskStatusDTO.java | 1 -
.../web/api/dto/status/StatusDescriptorDTO.java | 2 +-
.../nifi/web/api/dto/status/StatusHistoryDTO.java | 4 +-
.../nifi/web/api/dto/status/StatusSnapshotDTO.java | 2 +-
.../nifi/web/api/dto/util/DateTimeAdapter.java | 4 +-
.../nifi/web/api/dto/util/InstantAdapter.java | 1 +
.../apache/nifi/web/api/dto/util/TimeAdapter.java | 1 +
.../nifi/web/api/dto/util/TimestampAdapter.java | 4 +-
.../nifi/web/api/dto/util/TimezoneAdapter.java | 1 +
.../nifi/web/api/entity/AccessPolicyEntity.java | 4 +-
.../web/api/entity/AccessPolicySummaryEntity.java | 3 +-
.../apache/nifi/web/api/entity/ActionEntity.java | 4 +-
.../entity/ActivateControllerServicesEntity.java | 2 +-
.../web/api/entity/AffectedComponentEntity.java | 2 +-
.../web/api/entity/AnalyzeFlowRequestEntity.java | 3 +-
.../apache/nifi/web/api/entity/BannerEntity.java | 1 -
.../apache/nifi/web/api/entity/BulletinEntity.java | 4 +-
.../ClearBulletinsForGroupResultsEntity.java | 1 -
.../api/entity/ClearBulletinsRequestEntity.java | 1 -
.../web/api/entity/ClearBulletinsResultEntity.java | 2 +-
.../web/api/entity/ClusterSearchResultsEntity.java | 3 +-
.../nifi/web/api/entity/ClusterSummaryEntity.java | 3 +-
.../nifi/web/api/entity/ComponentEntity.java | 2 +-
.../web/api/entity/ComponentReferenceEntity.java | 3 +-
.../web/api/entity/ComponentRunStatusEntity.java | 2 +-
.../nifi/web/api/entity/ComponentStateEntity.java | 3 +-
.../entity/ComponentValidationResultEntity.java | 3 +-
.../entity/ComponentValidationResultsEntity.java | 2 +-
.../api/entity/ConfigurationAnalysisEntity.java | 3 +-
.../nifi/web/api/entity/ConnectionEntity.java | 2 +-
.../web/api/entity/ConnectionStatisticsEntity.java | 3 +-
.../web/api/entity/ConnectionStatusEntity.java | 3 +-
.../nifi/web/api/entity/ConnectionsEntity.java | 1 +
.../nifi/web/api/entity/ConnectorTypesEntity.java | 3 +-
.../web/api/entity/ControllerBulletinsEntity.java | 2 +-
.../api/entity/ControllerConfigurationEntity.java | 3 +-
.../web/api/entity/ControllerServiceEntity.java | 3 +-
...ontrollerServiceReferencingComponentEntity.java | 3 +-
...ntrollerServiceReferencingComponentsEntity.java | 4 +-
.../entity/ControllerServiceRunStatusEntity.java | 1 -
.../api/entity/ControllerServiceTypesEntity.java | 3 +-
.../web/api/entity/ControllerServicesEntity.java | 4 +-
.../web/api/entity/CopySnippetRequestEntity.java | 1 -
.../web/api/entity/CreateActiveRequestEntity.java | 1 -
.../nifi/web/api/entity/CurrentUserEntity.java | 2 +-
.../nifi/web/api/entity/DropRequestEntity.java | 1 -
.../web/api/entity/FlowAnalysisResultEntity.java | 2 +-
.../web/api/entity/FlowAnalysisRuleEntity.java | 1 -
.../entity/FlowAnalysisRuleRunStatusEntity.java | 1 -
.../api/entity/FlowAnalysisRuleTypesEntity.java | 2 +-
.../nifi/web/api/entity/FlowBreadcrumbEntity.java | 3 +-
.../nifi/web/api/entity/FlowComparisonEntity.java | 6 +-
.../web/api/entity/FlowConfigurationEntity.java | 3 +-
.../org/apache/nifi/web/api/entity/FlowEntity.java | 3 +-
.../apache/nifi/web/api/entity/FlowFileEntity.java | 3 +-
.../web/api/entity/FlowRegistryBucketEntity.java | 3 +-
.../web/api/entity/FlowRegistryBucketsEntity.java | 1 +
.../web/api/entity/FlowRegistryClientEntity.java | 3 +-
.../api/entity/FlowRegistryClientTypesEntity.java | 2 +-
.../web/api/entity/FlowRegistryClientsEntity.java | 4 +-
.../apache/nifi/web/api/entity/FunnelsEntity.java | 1 +
.../nifi/web/api/entity/InputPortsEntity.java | 1 +
.../web/api/entity/JmxMetricsResultsEntity.java | 2 +-
.../apache/nifi/web/api/entity/LabelEntity.java | 3 +-
.../apache/nifi/web/api/entity/LabelsEntity.java | 1 +
.../nifi/web/api/entity/ListingRequestEntity.java | 3 +-
.../api/entity/NodeReplayLastEventSnapshotDTO.java | 1 -
.../nifi/web/api/entity/OutputPortsEntity.java | 1 +
.../web/api/entity/ParameterContextEntity.java | 3 +-
.../entity/ParameterContextReferenceEntity.java | 3 +-
.../api/entity/ParameterContextUpdateEntity.java | 2 +-
.../ParameterContextUpdateRequestEntity.java | 3 +-
.../ParameterContextValidationRequestEntity.java | 3 +-
.../web/api/entity/ParameterContextsEntity.java | 4 +-
.../nifi/web/api/entity/ParameterEntity.java | 3 +-
.../entity/ParameterGroupConfigurationEntity.java | 2 +-
...ameterProviderApplyParametersRequestEntity.java | 3 +-
.../ParameterProviderConfigurationEntity.java | 3 +-
.../web/api/entity/ParameterProviderEntity.java | 4 +-
...arameterProviderParameterApplicationEntity.java | 2 +-
.../ParameterProviderParameterFetchEntity.java | 3 +-
...arameterProviderReferencingComponentEntity.java | 3 +-
...rameterProviderReferencingComponentsEntity.java | 1 +
.../api/entity/ParameterProviderTypesEntity.java | 2 +-
.../apache/nifi/web/api/entity/PeersEntity.java | 2 +-
.../org/apache/nifi/web/api/entity/PortEntity.java | 3 +-
.../nifi/web/api/entity/PortRunStatusEntity.java | 1 -
.../nifi/web/api/entity/PortStatusEntity.java | 3 +-
.../web/api/entity/PrioritizerTypesEntity.java | 3 +-
.../nifi/web/api/entity/ProcessGroupEntity.java | 3 +-
.../web/api/entity/ProcessGroupFlowEntity.java | 3 +-
.../web/api/entity/ProcessGroupImportEntity.java | 3 +-
.../entity/ProcessGroupReplaceRequestEntity.java | 3 +-
.../web/api/entity/ProcessGroupStatusEntity.java | 1 -
.../web/api/entity/ProcessGroupUploadEntity.java | 5 +-
.../nifi/web/api/entity/ProcessGroupsEntity.java | 1 +
.../web/api/entity/ProcessorDiagnosticsEntity.java | 4 +-
.../nifi/web/api/entity/ProcessorEntity.java | 3 +-
.../entity/ProcessorRunStatusDetailsEntity.java | 3 +-
.../web/api/entity/ProcessorRunStatusEntity.java | 1 -
.../nifi/web/api/entity/ProcessorStatusEntity.java | 3 +-
.../nifi/web/api/entity/ProcessorTypesEntity.java | 3 +-
.../nifi/web/api/entity/ProcessorsEntity.java | 1 +
.../entity/ProcessorsRunStatusDetailsEntity.java | 1 +
.../web/api/entity/ProvenanceOptionsEntity.java | 1 -
.../web/api/entity/RemotePortRunStatusEntity.java | 1 -
.../web/api/entity/RemoteProcessGroupEntity.java | 3 +-
.../api/entity/RemoteProcessGroupPortEntity.java | 3 +-
.../api/entity/RemoteProcessGroupStatusEntity.java | 3 +-
.../web/api/entity/RemoteProcessGroupsEntity.java | 1 +
.../api/entity/ReplayLastEventRequestEntity.java | 1 -
.../api/entity/ReplayLastEventResponseEntity.java | 2 +-
.../web/api/entity/ReplayLastEventSnapshotDTO.java | 2 +-
.../nifi/web/api/entity/ReportingTaskEntity.java | 4 +-
.../api/entity/ReportingTaskRunStatusEntity.java | 1 -
.../web/api/entity/ReportingTaskTypesEntity.java | 3 +-
.../nifi/web/api/entity/ReportingTasksEntity.java | 4 +-
.../nifi/web/api/entity/ResourcesEntity.java | 2 +-
.../api/entity/RunStatusDetailsRequestEntity.java | 2 +-
.../nifi/web/api/entity/RuntimeManifestEntity.java | 3 +-
.../web/api/entity/ScheduleComponentsEntity.java | 2 +-
.../apache/nifi/web/api/entity/SnippetEntity.java | 3 +-
.../entity/StartVersionControlRequestEntity.java | 3 +-
.../nifi/web/api/entity/StatusHistoryEntity.java | 3 +-
.../web/api/entity/SubmitReplayRequestEntity.java | 1 -
.../web/api/entity/TenantCollectionEntity.java | 1 +
.../apache/nifi/web/api/entity/TenantEntity.java | 3 +-
.../apache/nifi/web/api/entity/TenantsEntity.java | 1 +
.../web/api/entity/TransactionResultEntity.java | 2 +-
...ateControllerServiceReferenceRequestEntity.java | 2 +-
.../org/apache/nifi/web/api/entity/UserEntity.java | 3 +-
.../nifi/web/api/entity/UserGroupEntity.java | 3 +-
.../nifi/web/api/entity/UserGroupsEntity.java | 1 +
.../apache/nifi/web/api/entity/UsersEntity.java | 4 +-
.../web/api/entity/VerifyConfigRequestEntity.java | 3 +-
.../VersionControlComponentMappingEntity.java | 2 +-
.../entity/VersionControlInformationEntity.java | 3 +-
.../nifi/web/api/entity/VersionedFlowEntity.java | 3 +-
.../api/entity/VersionedFlowSnapshotEntity.java | 3 +-
.../VersionedFlowSnapshotMetadataEntity.java | 3 +-
.../VersionedFlowSnapshotMetadataSetEntity.java | 1 +
.../entity/VersionedFlowUpdateRequestEntity.java | 3 +-
.../nifi/web/api/entity/VersionedFlowsEntity.java | 1 +
.../VersionedReportingTaskImportRequestEntity.java | 3 +-
...VersionedReportingTaskImportResponseEntity.java | 2 +-
.../nifi-framework/nifi-file-authorizer/pom.xml | 16 +-
.../nifi/authorization/FileAccessPolicyMapper.java | 12 +-
.../FileAuthorizedUserGroupsMapper.java | 14 +-
.../FingerprintAccessPolicyMapper.java | 6 +-
.../FingerprintAuthorizedUserGroupsMapper.java | 6 +-
.../authorization/FileUserGroupProviderTest.java | 2 +-
.../nifi-flowfile-repo-serialization/pom.xml | 10 +-
.../WriteAheadRepositoryRecordSerde.java | 2 +-
.../repository/schema/ContentClaimFieldMap.java | 4 +-
.../repository/schema/ContentClaimSchema.java | 8 +-
.../repository/schema/FlowFileRecordFieldMap.java | 4 +-
.../repository/schema/FlowFileSchema.java | 6 +-
.../nifi-framework-authorization-providers/pom.xml | 8 +-
.../StandardAuthorizerConfigurationContext.java | 2 +-
.../authorization/StandardManagedAuthorizer.java | 6 +-
...CompositeConfigurableUserGroupProviderTest.java | 2 +-
.../CompositeUserGroupProviderTest.java | 4 +-
.../SimpleConfigurableUserGroupProvider.java | 2 +-
.../authorization/SimpleUserGroupProvider.java | 2 +-
.../StandardManagedAuthorizerTest.java | 2 +-
.../nifi/authorization/user/NiFiUserUtilsTest.java | 1 -
.../nifi-framework-authorization/pom.xml | 4 +-
.../authorization/resource/DataAuthorizable.java | 3 +-
.../authorization/resource/ResourceFactory.java | 34 +-
.../nifi/authorization/resource/ResourceType.java | 2 +-
.../resource/ProvenanceDataAuthorizableTest.java | 2 +-
.../nifi-framework-cluster-protocol/pom.xml | 12 +-
.../node/LeaderElectionNodeProtocolSender.java | 6 +-
.../coordination/node/NodeConnectionStatus.java | 7 +-
.../ClusterCoordinationProtocolSender.java | 6 +-
.../nifi/cluster/protocol/ComponentRevision.java | 2 +-
.../nifi/cluster/protocol/ConnectionResponse.java | 2 +-
.../apache/nifi/cluster/protocol/Heartbeat.java | 5 +-
.../nifi/cluster/protocol/HeartbeatPayload.java | 14 +-
.../nifi/cluster/protocol/NodeIdentifier.java | 6 +-
.../nifi/cluster/protocol/ProtocolListener.java | 4 +-
.../nifi/cluster/protocol/StandardDataFlow.java | 2 +-
.../ClusterCoordinationProtocolSenderListener.java | 10 +-
.../protocol/impl/SocketProtocolListener.java | 27 +-
.../cluster/protocol/jaxb/JaxbProtocolContext.java | 12 +-
.../jaxb/message/AdaptedConnectionRequest.java | 1 -
.../jaxb/message/AdaptedConnectionResponse.java | 2 +-
.../protocol/jaxb/message/AdaptedHeartbeat.java | 1 -
.../jaxb/message/AdaptedNodeConnectionStatus.java | 2 +-
.../protocol/jaxb/message/DataFlowAdapter.java | 1 -
.../jaxb/message/NodeConnectionStatusAdapter.java | 1 -
.../protocol/jaxb/message/ObjectFactory.java | 11 +-
.../message/ClusterWorkloadResponseMessage.java | 2 +-
.../protocol/message/ConnectionRequestMessage.java | 1 -
.../message/ConnectionResponseMessage.java | 3 +-
.../protocol/message/FlowResponseMessage.java | 1 -
.../cluster/protocol/message/HeartbeatMessage.java | 1 -
.../protocol/message/HeartbeatResponseMessage.java | 2 +-
.../NodeConnectionStatusResponseMessage.java | 1 -
.../protocol/message/NodeStatusChangeMessage.java | 1 -
.../cluster/protocol/message/OffloadMessage.java | 5 +-
.../nifi/cluster/protocol/message/PingMessage.java | 3 +-
.../message/ReconnectionRequestMessage.java | 4 +-
.../FrameworkClusterProtocolConfiguration.java | 2 +-
.../org/apache/nifi/io/socket/SocketListener.java | 11 +-
.../org/apache/nifi/io/socket/SocketUtils.java | 9 +-
.../protocol/impl/SocketProtocolListenerTest.java | 12 +-
.../jaxb/message/TestJaxbProtocolUtils.java | 2 +-
.../nifi-framework/nifi-framework-cluster/pom.xml | 38 +-
.../cluster/StandardClusterDetailsFactory.java | 1 -
.../heartbeat/AbstractHeartbeatMonitor.java | 4 +-
.../heartbeat/StandardNodeHeartbeat.java | 2 +-
.../coordination/http/EndpointResponseMerger.java | 4 +-
.../http/StandardHttpResponseMapper.java | 4 +-
.../http/endpoints/AbstractNodeStatusEndpoint.java | 6 +-
.../endpoints/ComponentStateEndpointMerger.java | 16 +-
.../ConnectorPropertyGroupEndpointMerger.java | 2 +-
.../ConnectorPropertyGroupNamesEndpointMerger.java | 2 +-
.../http/endpoints/CountersEndpointMerger.java | 10 +-
.../http/endpoints/DropRequestEndpointMerger.java | 10 +-
.../endpoints/FlowAnalysisRuleEndpointMerger.java | 8 +-
.../endpoints/FlowAnalysisRulesEndpointMerger.java | 2 +-
.../http/endpoints/FunnelEndpointMerger.java | 2 +-
.../http/endpoints/LabelEndpointMerger.java | 2 +-
.../endpoints/ListFlowFilesEndpointMerger.java | 16 +-
.../ParameterContextUpdateEndpointMerger.java | 2 +-
.../ProcessorDiagnosticsEndpointMerger.java | 12 +-
.../ProcessorRunStatusDetailsEndpointMerger.java | 2 +-
.../endpoints/ProvenanceEventEndpointMerger.java | 10 +-
.../endpoints/SystemDiagnosticsEndpointMerger.java | 10 +-
.../VersionControlInformationEndpointMerger.java | 10 +-
.../http/replication/AsyncClusterResponse.java | 6 +-
.../http/replication/HttpReplicationClient.java | 4 +-
.../replication/RequestCompletionCallback.java | 4 +-
.../http/replication/ResponseUtils.java | 4 +-
.../replication/StandardAsyncClusterResponse.java | 12 +-
.../replication/ThreadPoolRequestReplicator.java | 9 +-
.../client/StandardHttpReplicationClient.java | 3 +-
.../http/replication/io/JsonEntitySerializer.java | 6 +-
.../http/replication/io/ReplicatedResponse.java | 27 +-
.../http/replication/io/XmlEntitySerializer.java | 8 +-
.../coordination/node/NodeClusterCoordinator.java | 4 +-
.../coordination/node/NodeStateManager.java | 4 +-
.../java/org/apache/nifi/cluster/event/Event.java | 4 +-
.../impl/FileBasedClusterNodeFirewall.java | 12 +-
.../nifi/cluster/manager/BulletinMerger.java | 2 +-
.../manager/FlowAnalysisRuleEntityMerger.java | 2 +-
.../nifi/cluster/manager/FunnelEntityMerger.java | 2 +-
.../apache/nifi/cluster/manager/NodeResponse.java | 13 +-
.../VersionControlInformationEntityMerger.java | 4 +-
.../FrameworkClusterConfiguration.java | 2 +-
.../endpoints/AccessPolicyEndpointMergerTest.java | 2 +-
.../endpoints/CurrentUserEndpointMergerTest.java | 2 +-
.../http/replication/TestResponseUtils.java | 2 +-
.../TestThreadPoolRequestReplicator.java | 8 +-
.../replication/io/TestJsonEntitySerializer.java | 18 +-
.../replication/util/MockReplicationClient.java | 17 +-
.../manager/AccessPolicyEntityMergerTest.java | 2 +-
.../manager/ControllerServiceEntityMergerTest.java | 2 +-
.../manager/PropertyDescriptorDtoMergerTest.java | 2 +-
.../manager/ReportingTaskEntityMergerTest.java | 2 +-
.../nifi/cluster/manager/UserEntityMergerTest.java | 2 +-
.../cluster/manager/UserGroupEntityMergerTest.java | 2 +-
.../nifi-framework-components/pom.xml | 40 +-
...dardFlowActionReporterConfigurationContext.java | 2 +-
.../secrets/ParameterProviderSecretsManager.java | 3 +-
.../validation/StandardValidationTrigger.java | 6 +-
.../nifi/controller/StandardProcessorNode.java | 4 +-
.../AbstractFlowAnalysisRuleContext.java | 4 +-
.../flowanalysis/AbstractFlowAnalysisRuleNode.java | 2 +-
.../parameter/StandardParameterProviderNode.java | 64 +-
.../repository/StandardCounterRepository.java | 6 +-
.../repository/StandardProcessSession.java | 18 +-
.../repository/TransientClaimRepositoryRecord.java | 4 +-
.../repository/io/ContentClaimOutputStream.java | 2 +-
.../repository/io/DisableOnCloseInputStream.java | 2 +-
.../repository/io/FlowFileAccessInputStream.java | 8 +-
.../repository/io/FlowFileAccessOutputStream.java | 6 +-
.../repository/io/TaskTerminationInputStream.java | 6 +-
.../repository/io/TaskTerminationOutputStream.java | 6 +-
.../controller/service/ServiceStateTransition.java | 5 +
.../service/StandardControllerServiceNode.java | 31 +-
.../service/StandardControllerServiceProvider.java | 34 +-
.../StandardControllerServiceReference.java | 35 +-
.../service/StandardControllerServiceResolver.java | 64 +-
.../nifi/controller/state/StandardStateMap.java | 3 +-
...StandardStateProviderInitializationContext.java | 11 +-
.../state/config/StateManagerConfiguration.java | 17 +-
.../manager/StandardStateManagerProvider.java | 31 +-
.../StandardVersionedComponentSynchronizer.java | 2 +-
.../apache/nifi/groups/VersionControlFields.java | 4 +-
.../nifi/parameter/StandardParameterContext.java | 2 +-
.../ComponentSpecificControllerServiceLookup.java | 4 +-
.../StandardValidationContextFactory.java | 2 +-
.../extension/AbstractExtensionBundleMetadata.java | 2 +-
.../extension/AbstractExtensionRegistry.java | 2 +-
.../extension/NiFiRegistryExtensionRegistry.java | 10 +-
.../NiFiRegistryExternalResourceProvider.java | 2 +-
.../flow/StandardFlowRegistryClientNode.java | 2 +-
.../flow/StandardVersionControlInformation.java | 4 +-
.../mapping/InstantiatedVersionedProcessGroup.java | 2 +-
.../flow/mapping/StandardComparableDataFlow.java | 4 +-
.../flow/mapping/VersionedComponentFlowMapper.java | 54 +-
.../apache/nifi/util/FlowDifferenceFilters.java | 4 +-
.../repository/StandardCounterRepositoryTest.java | 2 +-
.../repository/StandardProvenanceReporterTest.java | 1 -
.../repository/TestStandardFlowFileRecord.java | 6 +-
.../metrics/TestSecondPrecisionEventContainer.java | 2 +-
.../StandardControllerServiceResolverTest.java | 48 +
...StandardControllerServiceInvocationHandler.java | 4 +-
.../nifi/logging/TestStandardLogRepository.java | 2 +-
.../nifi/logging/TestStandardLoggingContext.java | 2 +-
.../nifi/processor/TestStandardPropertyValue.java | 5 +-
.../flow/FlowAnalyzingRegistryClientNodeTest.java | 9 +-
.../src/test/resources/conf/nifi.properties | 2 +-
.../web-client-service-provider.json | 120 +
.../nifi-framework/nifi-framework-core-api/pom.xml | 18 +-
.../apache/nifi/asset/AssetComponentManager.java | 10 +
.../ConnectorRepositoryInitializationContext.java | 2 +-
.../nifi/controller/FlowAnalysisRuleNode.java | 4 +-
.../nifi/controller/ParameterProviderNode.java | 2 +-
.../apache/nifi/controller/ReloadComponent.java | 2 +-
.../org/apache/nifi/controller/WorkerQueue.java | 4 +-
.../exception/CommunicationsException.java | 1 -
.../ControllerServiceInstantiationException.java | 1 -
.../nifi/controller/flow/VersionedDataflow.java | 2 +-
.../FlowAnalysisRuleInstantiationException.java | 2 +-
.../controller/repository/CounterRepository.java | 4 +-
.../controller/scheduling/SchedulingAgent.java | 6 +-
.../controller/service/ControllerServiceNode.java | 2 +-
.../service/ControllerServiceReference.java | 4 +-
.../java/org/apache/nifi/groups/ProcessGroup.java | 2 +-
.../org/apache/nifi/groups/ProcessGroupCounts.java | 18 +-
.../nifi/groups/RemoteProcessGroupCounts.java | 3 +-
.../apache/nifi/logging/LogRepositoryFactory.java | 3 +-
.../nifi/manifest/RuntimeManifestService.java | 2 +-
.../extension/ExtensionBundleMetadata.java | 2 +-
.../extension/ExtensionRegistryException.java | 2 +-
.../nifi/registry/flow/FlowRegistryClientNode.java | 2 +-
...ardFlowRegistryClientInitializationContext.java | 2 +-
.../nifi/remote/protocol/ServerProtocol.java | 6 +-
.../org/apache/nifi/validation/RuleViolation.java | 8 +-
.../apache/nifi/web/revision/RevisionClaim.java | 4 +-
.../apache/nifi/web/revision/RevisionManager.java | 1 -
.../apache/nifi/web/revision/RevisionUpdate.java | 4 +-
.../apache/nifi/controller/TestStandardFunnel.java | 2 +-
.../apache/nifi/util/CharacterFilterUtilsTest.java | 3 +-
.../nifi-framework/nifi-framework-core/pom.xml | 76 +-
.../nifi/asset/StandardAssetComponentManager.java | 58 +
.../nifi/asset/StandardAssetSynchronizer.java | 54 +-
.../org/apache/nifi/cluster/BulletinsPayload.java | 16 +-
...StandardConnectorRepoInitializationContext.java | 2 +-
.../connector/StandardConnectorRepository.java | 4 +-
.../ClusterCoordinatorNodeInformant.java | 8 +-
.../apache/nifi/controller/ExtensionBuilder.java | 1912 ++---
.../org/apache/nifi/controller/FlowController.java | 8 +-
.../controller/RingBufferGarbageCollectionLog.java | 6 +-
.../org/apache/nifi/controller/SnippetManager.java | 12 +-
.../apache/nifi/controller/StandardSnippet.java | 8 +-
.../nifi/controller/cluster/Heartbeater.java | 4 +-
.../exception/FlowFileConsumptionException.java | 1 -
.../flowanalysis/StandardFlowAnalysisRuleNode.java | 6 +-
.../inheritance/BundleCompatibilityCheck.java | 4 +-
.../TaskTerminationAwareStateManager.java | 6 +-
.../controller/queue/AbstractFlowFileQueue.java | 12 +
.../nifi/controller/queue/DropFlowFileAction.java | 4 +-
.../queue/DropFlowFileRepositoryRecord.java | 6 +-
.../nifi/controller/queue/QueuePrioritizer.java | 2 +-
.../controller/queue/SwappablePriorityQueue.java | 1 -
.../queue/clustered/FlowFileContentAccess.java | 4 +-
.../clustered/SocketLoadBalancedFlowFileQueue.java | 2 +-
.../client/async/nio/LoadBalanceSession.java | 1 -
.../async/nio/NioAsyncLoadBalanceClient.java | 23 +-
.../clustered/client/async/nio/PeerChannel.java | 10 +-
.../clustered/partition/LocalQueuePartition.java | 2 +-
.../SwappablePriorityQueueLocalPartition.java | 2 +-
.../server/ClusterLoadBalanceAuthorizer.java | 4 +-
.../server/ConnectionLoadBalanceServer.java | 8 +-
.../clustered/server/LoadBalanceAuthorizer.java | 2 +-
.../server/StandardLoadBalanceProtocol.java | 4 +-
.../repository/BatchingSessionFactory.java | 21 +-
.../repository/NonPurgeableContentRepository.java | 5 +-
.../repository/RepositoryPurgeException.java | 1 -
.../serialization/FlowEncodingVersion.java | 4 +-
.../controller/serialization/FlowSerializer.java | 4 +-
.../serialization/RunningComponentSetFilter.java | 3 +-
.../serialization/VersionedDataflowMapper.java | 9 +-
.../serialization/VersionedFlowSynchronizer.java | 33 +-
.../nifi/controller/state/StateMapSerDe.java | 8 +-
.../state/providers/AbstractStateProvider.java | 4 +-
.../local/WriteAheadLocalStateProvider.java | 34 +-
.../CachingConnectionStatusAnalyticsEngine.java | 8 +-
.../analytics/ConnectionStatusAnalytics.java | 16 +-
.../analytics/ConnectionStatusAnalyticsEngine.java | 4 +-
.../analytics/StatusAnalyticsModelMapFactory.java | 24 +-
.../analytics/StatusMetricExtractFunction.java | 4 +-
.../models/BivariateStatusAnalyticsModel.java | 3 +-
.../status/analytics/models/SimpleRegression.java | 9 +-
.../controller/swap/SchemaSwapDeserializer.java | 12 +-
.../nifi/controller/swap/SchemaSwapSerializer.java | 16 +-
.../controller/swap/SimpleSwapDeserializer.java | 18 +-
.../nifi/controller/swap/StandardSwapContents.java | 8 +-
.../nifi/controller/swap/StandardSwapSummary.java | 8 +-
.../nifi/controller/swap/SwapDeserializer.java | 6 +-
.../apache/nifi/controller/swap/SwapSchema.java | 8 +-
.../nifi/controller/swap/SwapSerializer.java | 6 +-
.../nifi/controller/swap/SwapSummaryFieldMap.java | 10 +-
.../controller/tasks/ReportingTaskWrapper.java | 2 +-
.../bootstrap/tasks/DiagnosticAnalysisTask.java | 2 +-
.../tasks/OperatingSystemDiagnosticTask.java | 8 +-
.../tasks/ProcessorTimingDiagnosticTask.java | 2 +-
.../events/NodeBulletinProcessingStrategy.java | 6 +-
.../nifi/flowanalysis/GhostFlowAnalysisRule.java | 1 +
.../configuration/FlowControllerConfiguration.java | 13 +-
.../configuration/ManagerConfiguration.java | 2 +-
.../configuration/SslContextConfiguration.java | 31 +-
...ration.java => TaskSchedulerConfiguration.java} | 24 +-
.../framework/ssl/FrameworkKeyManagerBuilder.java | 2 +-
.../framework/ssl/FrameworkSslContextHolder.java | 2 +-
.../framework/ssl/FrameworkSslContextProvider.java | 8 +-
.../ssl/FrameworkTrustManagerBuilder.java | 2 +-
.../ssl/SecurityStoreChangedPathListener.java | 4 +-
.../nifi/groups/StandardStatelessGroupNode.java | 2 +-
.../java/org/apache/nifi/jaxb/AdaptedBulletin.java | 4 +-
.../java/org/apache/nifi/jaxb/BulletinAdapter.java | 1 -
.../persistence/StandardFlowConfigurationDAO.java | 19 +-
.../persistence/StandardSnippetDeserializer.java | 8 +-
.../persistence/StandardSnippetSerializer.java | 8 +-
.../org/apache/nifi/processor/GhostProcessor.java | 10 +-
.../apache/nifi/reporting/GhostReportingTask.java | 8 +-
.../main/java/org/apache/nifi/util/FlowInfo.java | 2 +-
.../apache/nifi/validation/RuleViolationKey.java | 8 +-
.../validation/StandardRuleViolationsManager.java | 2 +-
.../apache/nifi/cluster/HeartbeatPayloadTest.java | 12 +-
.../controller/DummyPrimaryNodeOnlyProcessor.java | 12 +-
.../nifi/controller/DummyScheduledProcessor.java | 1 -
.../controller/DummyScheduledReportingTask.java | 8 +-
.../nifi/controller/TestStandardFlowFileQueue.java | 2 +-
.../controller/queue/QueuePrioritizerTest.java | 2 +-
.../queue/clustered/LoadBalancedQueueIT.java | 4 +-
.../queue/clustered/TestNaiveLimitThreshold.java | 2 +-
.../TestSocketLoadBalancedFlowFileQueue.java | 4 +-
.../client/async/nio/TestPeerChannel.java | 10 +-
.../server/ClusteredLoadBalanceAuthorizerTest.java | 4 +-
.../server/ConnectionLoadBalanceServerTest.java | 10 +-
.../repository/StandardProcessSessionIT.java | 33 +-
.../repository/TestFileSystemRepository.java | 55 +-
.../repository/io/TestLimitedInputStream.java | 2 +-
.../nifi/controller/repository/util/DiskUtils.java | 6 +-
.../controller/scheduling/LongEnablingService.java | 4 +-
.../scheduling/TestStandardProcessScheduler.java | 45 +-
.../processors/FailOnScheduledProcessor.java | 6 +-
.../VersionedFlowSynchronizerTest.java | 288 +
.../StandardControllerServiceProviderTest.java | 2 +-
.../TestStandardControllerServiceProvider.java | 109 +-
.../controller/service/mock/DummyProcessor.java | 6 +-
.../nifi/controller/service/mock/ServiceA.java | 4 +-
.../nifi/controller/service/mock/ServiceC.java | 4 +-
.../service/util/TestControllerService.java | 6 +-
.../state/providers/AbstractTestStateProvider.java | 8 +-
.../local/TestWriteAheadLocalStateProvider.java | 4 +-
.../analytics/TestConnectionStatusAnalytics.java | 38 +-
.../TestStatusAnalyticsModelMapFactory.java | 14 +-
.../JsonNodeStatusHistoryDumpFactoryTest.java | 1 -
.../status/history/StatusHistoryUtilTest.java | 2 +-
...ComponentStatusRepositoryForComponentsTest.java | 396 +-
.../apache/nifi/controller/swap/MockFlowFile.java | 12 +-
.../shell/result/LineSplittingResultTest.java | 2 +-
.../shell/result/SingleLineResultTest.java | 2 +-
.../events/TestNodeBulletinProcessingStrategy.java | 6 +-
.../TestFlowConfigurationArchiveManager.java | 2 +-
.../processors/DataGeneratorTestProcessor.java | 13 +-
.../processors/StubAttributeLoggerProcessor.java | 12 +-
.../src/test/resources/conf/nifi.properties | 2 +-
.../resources/flowcontrollertest.nifi.properties | 2 +-
.../nifi-framework-external-resource-utils/pom.xml | 8 +-
.../ExternalResourceProviderServiceBuilder.java | 2 +-
...ernalResourceProviderInitializationContext.java | 2 +-
.../CollisionAwareResourceProviderWorkerTest.java | 1 -
...mpositeExternalResourceProviderServiceTest.java | 2 +-
.../DoNotReplaceResolutionStrategyTest.java | 2 +-
.../ReplaceWithNewerResolutionStrategyTest.java | 2 +-
.../nifi-framework-leader-election-shared/pom.xml | 4 +-
.../nifi-framework-nar-loading-utils/pom.xml | 6 +-
.../org/apache/nifi/nar/NarAutoLoaderTask.java | 2 +-
.../apache/nifi/nar/TestLoadNativeLibFromNar.java | 11 +-
.../nar/TestLoadNativeLibViaSystemProperty.java | 16 +-
.../java/org/apache/nifi/nar/TestNarLoader.java | 1 -
.../nifi-framework-nar-utils/pom.xml | 2 +-
.../apache/nifi/FlowRegistryClientInitializer.java | 15 -
.../init/ConfigurableComponentInitializer.java | 7 -
.../nifi/init/ControllerServiceInitializer.java | 17 -
.../nifi/init/FlowAnalysisRuleInitializer.java | 15 -
.../nifi/init/ParameterProviderInitializer.java | 15 -
.../org/apache/nifi/init/ProcessorInitializer.java | 17 -
.../java/org/apache/nifi/init/ReflectionUtils.java | 118 -
.../apache/nifi/init/ReportingTaskInitializer.java | 15 -
...MockControllerServiceInitializationContext.java | 2 +-
...ockFlowRegistryClientInitializationContext.java | 2 +-
.../org/apache/nifi/mock/MockProcessContext.java | 2 +-
.../org/apache/nifi/nar/InstanceClassLoader.java | 2 +
.../java/org/apache/nifi/nar/NarCloseable.java | 4 +-
.../nifi/nar/NarThreadContextClassLoader.java | 35 +-
.../nar/StandardExtensionDiscoveringManager.java | 2 +-
.../apache/nifi/nar/TestInstanceClassLoader.java | 4 +-
.../nifi-framework-status-history-shared/pom.xml | 2 +-
.../status/history/ConnectionStatusDescriptor.java | 8 +-
.../status/history/NodeStatusDescriptor.java | 70 +-
.../history/ProcessGroupStatusDescriptor.java | 8 +-
.../status/history/ProcessorStatusDescriptor.java | 390 +-
.../RemoteProcessGroupStatusDescriptor.java | 44 +-
.../nifi-framework/nifi-headless-server/pom.xml | 16 +-
.../apache/nifi/headless/HeadlessNiFiServer.java | 2 +-
.../nifi-framework/nifi-mock-authorizer/pom.xml | 2 +-
.../nifi-framework/nifi-nar-utils/pom.xml | 2 +-
.../java/org/apache/nifi/nar/NarClassLoader.java | 6 +-
.../java/org/apache/nifi/nar/NarClassLoaders.java | 6 +-
.../main/java/org/apache/nifi/nar/NarUnpacker.java | 2 +-
.../main/java/org/apache/nifi/util/FileUtils.java | 5 +-
.../java/org/apache/nifi/util/ReflectionUtils.java | 29 +-
.../nifi-framework/nifi-properties-loader/pom.xml | 2 +-
.../nifi/properties/NiFiPropertiesLoader.java | 1 -
.../properties/conf/duplicates.nifi.properties | 2 +-
.../nifi-framework/nifi-repository-models/pom.xml | 2 +-
.../repository/StandardRepositoryRecord.java | 2 +-
.../claim/TestStandardResourceClaimManager.java | 10 +-
.../nifi-framework/nifi-resources/NOTICE | 2 +-
.../nifi-framework/nifi-resources/pom.xml | 2 +-
.../src/main/resources/conf/logback.xml | 4 +-
.../src/main/resources/conf/nifi.properties | 2 +-
.../src/main/resources/conf/zookeeper.properties | 2 +-
.../nifi-framework/nifi-runtime/pom.xml | 2 +-
.../src/main/java/org/apache/nifi/NiFi.java | 2 +-
.../nifi-framework/nifi-site-to-site/pom.xml | 18 +-
.../apache/nifi/remote/HttpRemoteSiteListener.java | 1 -
.../nifi/remote/PeerDescriptionModifier.java | 18 +-
.../apache/nifi/remote/RemoteResourceFactory.java | 10 +-
.../apache/nifi/remote/RemoteResourceManager.java | 11 +-
.../nifi/remote/SocketRemoteSiteListener.java | 35 +-
.../nifi/remote/StandardRemoteGroupPort.java | 2 +-
.../protocol/AbstractFlowFileServerProtocol.java | 2 +-
.../http/StandardHttpFlowFileServerProtocol.java | 3 +-
.../socket/SocketFlowFileServerProtocol.java | 19 +-
.../nifi/remote/TestSocketRemoteSiteListener.java | 1 +
.../apache/nifi/remote/TestStandardPublicPort.java | 5 +-
.../nifi/remote/TestStandardRemoteGroupPort.java | 2 +-
.../socket/TestSocketFlowFileServerProtocol.java | 2 +-
.../nifi-standard-prioritizers/pom.xml | 2 +-
.../nifi-framework/nifi-user-actions/pom.xml | 2 +-
.../org/apache/nifi/action/FlowChangeAction.java | 1 +
.../nifi-framework/nifi-web/nifi-jetty/pom.xml | 26 +-
.../org/apache/nifi/web/server/JettyServer.java | 83 +-
.../nifi/web/server/StandardServerProvider.java | 2 +-
.../connector/FrameworkServerConnectorFactory.java | 2 +-
.../filter/DataTransferExcludedDoSFilter.java | 4 +-
.../filter/StandardRequestFilterProvider.java | 2 +-
.../web/server/handler/HeaderWriterHandler.java | 3 +-
.../server/log/RequestAuthenticationFilter.java | 14 +-
.../web/server/StandardServerProviderTest.java | 4 +-
.../FrameworkServerConnectorFactoryTest.java | 4 +-
.../filter/DataTransferExcludedDoSFilterTest.java | 10 +-
.../filter/RestApiRequestFilterProviderTest.java | 2 +-
.../filter/StandardRequestFilterProviderTest.java | 2 +-
.../nifi-web/nifi-ui-extension/pom.xml | 2 +-
.../ui/extension/contentviewer/ContentViewer.java | 8 +-
.../nifi-framework/nifi-web/nifi-ui/pom.xml | 4 +-
.../nifi-ui/src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-framework/nifi-web/nifi-web-api/pom.xml | 39 +-
.../org/apache/nifi/audit/ProcessGroupAuditor.java | 38 +
.../org/apache/nifi/audit/ProcessorAuditor.java | 20 +-
.../nifi/audit/RemoteProcessGroupAuditor.java | 25 +-
.../AuthorizeControllerServiceReference.java | 2 +-
.../authorization/AuthorizeParameterReference.java | 4 +-
.../nifi/prometheusutil/PrometheusMetricsUtil.java | 20 +
.../nifi/prometheusutil/VersionInfoRegistry.java | 92 +
.../org/apache/nifi/web/NiFiServiceFacade.java | 2 +-
.../apache/nifi/web/NiFiWebApiConfiguration.java | 2 +-
.../apache/nifi/web/NiFiWebApiResourceConfig.java | 5 +-
.../apache/nifi/web/StandardNiFiContentAccess.java | 9 +-
.../apache/nifi/web/StandardNiFiServiceFacade.java | 26 +-
.../web/StandardNiFiWebConfigurationContext.java | 9 +-
.../nifi/web/api/AbstractParameterResource.java | 3 +-
.../apache/nifi/web/api/ApplicationResource.java | 31 +-
.../apache/nifi/web/api/ControllerResource.java | 14 +-
.../nifi/web/api/ControllerServiceResource.java | 8 +-
.../java/org/apache/nifi/web/api/FlowResource.java | 37 +-
.../apache/nifi/web/api/FlowUpdateResource.java | 9 +-
.../nifi/web/api/ParameterProviderResource.java | 10 +-
.../org/apache/nifi/web/api/ProcessorResource.java | 8 +-
.../apache/nifi/web/api/ProvenanceResource.java | 1 -
.../nifi/web/api/RemoteProcessGroupResource.java | 8 +-
.../apache/nifi/web/api/ReportingTaskResource.java | 8 +-
.../api/config/AccessDeniedExceptionMapper.java | 9 +-
.../config/AuthorizationAccessExceptionMapper.java | 7 +-
.../web/api/config/ClusterExceptionMapper.java | 1 -
...onnectingNodeMutableRequestExceptionMapper.java | 4 +-
.../api/config/IllegalArgumentExceptionMapper.java | 5 +-
.../config/IllegalNodeOffloadExceptionMapper.java | 7 +-
.../api/config/IllegalStateExceptionMapper.java | 5 +-
.../web/api/config/InterruptedExceptionMapper.java | 1 -
.../api/config/InvalidRevisionExceptionMapper.java | 9 +-
.../JsonContentConversionExceptionMapper.java | 9 +-
.../web/api/config/JsonMappingExceptionMapper.java | 9 +-
.../web/api/config/JsonParseExceptionMapper.java | 4 +-
.../web/api/config/NiFiCoreExceptionMapper.java | 7 +-
.../NoClusterCoordinatorExceptionMapper.java | 1 -
.../web/api/config/NotFoundExceptionMapper.java | 7 +-
.../config/RangeNotSatisfiableExceptionMapper.java | 7 +-
.../config/ResourceNotFoundExceptionMapper.java | 9 +-
.../web/api/config/ValidationExceptionMapper.java | 2 +-
.../api/config/WebApplicationExceptionMapper.java | 7 +-
.../org/apache/nifi/web/api/dto/DtoFactory.java | 9024 ++++++++++----------
.../org/apache/nifi/web/api/dto/EntityFactory.java | 14 +-
.../web/api/filter/RedirectResourceFilter.java | 5 +-
.../metrics/AbstractPrometheusMetricsWriter.java | 1 +
.../metrics/JsonFormatPrometheusMetricsWriter.java | 2 +-
.../web/api/metrics/jmx/JmxMetricsCollector.java | 10 +-
.../api/metrics/jmx/JmxMetricsResultConverter.java | 4 +-
.../nifi/web/api/request/ClientIdParameter.java | 3 +-
.../nifi/web/api/request/FlowMetricsRegistry.java | 5 +-
.../api/request/FlowMetricsReportingStrategy.java | 2 +-
.../configuration/WebApplicationConfiguration.java | 4 +-
.../ApplicationStartupContextListener.java | 4 +-
.../nifi/web/controller/ControllerFacade.java | 2 +-
.../web/controller/ControllerSearchService.java | 6 +-
.../apache/nifi/web/dao/impl/AbstractPortDAO.java | 2 +-
.../nifi/web/dao/impl/StandardConnectionDAO.java | 7 +-
.../web/dao/impl/StandardControllerServiceDAO.java | 45 +-
.../nifi/web/dao/impl/StandardFlowRegistryDAO.java | 12 +-
.../dao/impl/StandardPolicyBasedAuthorizerDAO.java | 6 +-
.../apache/nifi/web/filter/ExceptionFilter.java | 3 +-
.../search/attributematchers/BundleMatcher.java | 2 +-
.../search/attributematchers/PropertyMatcher.java | 4 +-
.../apache/nifi/web/search/query/SearchQuery.java | 2 +-
.../apache/nifi/web/util/ClientResponseUtils.java | 2 +-
.../util/ClusterReplicationComponentLifecycle.java | 12 +-
.../nifi/web/util/ParameterContextReplacer.java | 2 +-
.../apache/nifi/audit/TestProcessGroupAuditor.java | 137 +-
.../apache/nifi/audit/TestProcessorAuditor.java | 1 -
.../nifi/web/StandardNiFiServiceFacadeTest.java | 23 +-
.../nifi/web/api/TestApplicationResource.java | 4 +-
.../nifi/web/api/TestDataTransferResource.java | 18 +-
.../org/apache/nifi/web/api/TestFlowResource.java | 114 +-
.../nifi/web/api/TestProcessGroupResource.java | 6 +-
.../nifi/web/api/TestSiteToSiteResource.java | 6 +-
.../apache/nifi/web/api/TestVersionsResource.java | 4 +-
.../JsonContentConversionExceptionMapperTest.java | 2 +-
.../web/api/filter/TestRedirectResourceFilter.java | 6 +-
.../web/api/metrics/jmx/JmxMetricsFilterTest.java | 2 +-
.../metrics/jmx/JmxMetricsResultConverterTest.java | 12 +-
.../StandardNiFiConnectorWebContextTest.java | 6 +-
.../dao/impl/TestStandardParameterContextDAO.java | 2 +-
.../web/dao/impl/TestStandardProcessGroupDAO.java | 2 +-
.../impl/TestStandardRemoteProcessGroupDAO.java | 2 +-
.../search/AttributeBasedComponentMatcherTest.java | 2 +-
.../attributematchers/AttributeMatcherTest.java | 14 +-
.../attributematchers/BackPressureMatcherTest.java | 2 +-
.../search/attributematchers/BasicMatcherTest.java | 2 +-
.../attributematchers/ConnectionMatcherTest.java | 2 +-
.../ControllerServiceNodeMatcherTest.java | 2 +-
.../attributematchers/ExecutionMatcherTest.java | 2 +-
.../attributematchers/ExpirationMatcherTest.java | 2 +-
.../ParameterContextMatcherTest.java | 2 +-
.../PortScheduledStateMatcherTest.java | 2 +-
.../ProcessorMetadataMatcherTest.java | 3 +-
.../attributematchers/PropertyMatcherTest.java | 2 +-
.../attributematchers/RelationshipMatcherTest.java | 2 +-
.../RemoteProcessGroupMatcherTest.java | 2 +-
.../ScheduledStateMatcherTest.java | 2 +-
.../attributematchers/SchedulingMatcherTest.java | 2 +-
.../attributematchers/SearchableMatcherTest.java | 2 +-
.../attributematchers/TargetUriMatcherTest.java | 2 +-
.../TransmissionStatusMatcherTest.java | 2 +-
.../search/query/RegexSearchQueryParserTest.java | 3 +-
.../ComponentSearchResultEnricherTest.java | 2 +-
.../nifi/web/util/ClosedOpenIntervalTest.java | 2 +-
.../apache/nifi/web/util/PaginationHelperTest.java | 2 +-
.../ParameterContextNameCollisionResolverTest.java | 12 +-
.../web/util/ParameterContextReplacerTest.java | 2 +-
.../nifi/web/util/ResponseBuilderUtilsTest.java | 2 -
.../org/apache/nifi/web/util/SnippetUtilsTest.java | 1 -
.../nifi-web/nifi-web-optimistic-locking/pom.xml | 6 +-
.../nifi/web/revision/RevisionComparator.java | 4 +-
.../nifi/web/revision/StandardRevisionClaim.java | 4 +-
.../nifi/web/revision/StandardRevisionUpdate.java | 6 +-
.../nifi-web/nifi-web-security/pom.xml | 36 +-
.../web/security/NiFiAuthenticationFilter.java | 12 +-
.../security/NiFiAuthenticationRequestToken.java | 4 +-
.../nifi/web/security/ProxiedEntitiesUtils.java | 17 +-
.../security/StandardAuthenticationEntryPoint.java | 4 +-
.../NiFiAnonymousAuthenticationFilter.java | 3 +-
.../ClientRegistrationConfiguration.java | 2 +-
.../JwtAuthenticationSecurityConfiguration.java | 30 +-
.../configuration/OidcSecurityConfiguration.java | 24 +-
.../SamlAuthenticationSecurityConfiguration.java | 16 +-
.../configuration/WebSecurityConfiguration.java | 4 +-
.../security/cookie/ApplicationCookieService.java | 1 +
.../cookie/StandardApplicationCookieService.java | 6 +-
.../security/csrf/CsrfCookieRequestMatcher.java | 4 +-
.../security/csrf/SkipReplicatedCsrfFilter.java | 8 +-
.../csrf/StandardCookieCsrfTokenRepository.java | 6 +-
.../jwt/key/StandardJWSVerifierFactory.java | 2 +-
.../jwt/resolver/StandardBearerTokenResolver.java | 5 +-
.../jwt/revocation/JwtRevocationValidator.java | 2 +-
.../web/security/log/AuthenticationUserFilter.java | 8 +-
.../web/security/logout/StandardLogoutFilter.java | 8 +-
.../StandardOidcIdTokenDecoderFactory.java | 6 +-
.../client/web/OidcBearerTokenRefreshFilter.java | 8 +-
.../StandardAuthorizationRequestRepository.java | 4 +-
...StandardOAuth2AuthorizationRequestResolver.java | 2 +-
.../StandardOidcAuthorizedClientRepository.java | 4 +-
.../StandardAuthorizedClientConverter.java | 2 +-
.../oidc/logout/OidcLogoutSuccessHandler.java | 6 +-
.../StandardClientRegistrationProvider.java | 8 +-
.../OidcAuthenticationSuccessHandler.java | 4 +-
.../web/security/requests/ContentLengthFilter.java | 10 +-
.../StandardRegistrationBuilderProvider.java | 4 +-
...StandardRelyingPartyRegistrationRepository.java | 4 +-
.../StandardRelyingPartyRegistrationResolver.java | 2 +-
...andardSaml2AuthenticationRequestRepository.java | 4 +-
.../Saml2AuthenticationSuccessHandler.java | 4 +-
.../logout/Saml2LocalLogoutFilter.java | 1 +
.../logout/Saml2LogoutSuccessHandler.java | 4 +-
.../logout/Saml2SingleLogoutFilter.java | 8 +-
.../logout/Saml2SingleLogoutHandler.java | 5 +-
.../StandardSaml2LogoutRequestRepository.java | 4 +-
.../spring/LoginIdentityProviderFactoryBean.java | 31 +-
...dLoginIdentityProviderConfigurationContext.java | 3 +-
.../x509/SubjectDnX509PrincipalExtractor.java | 4 +-
.../security/x509/X509AuthenticationFilter.java | 2 +-
.../security/x509/X509AuthenticationProvider.java | 2 +-
.../security/x509/X509CertificateExtractor.java | 3 +-
.../StandardAuthenticationEntryPointTest.java | 6 +-
.../StandardApplicationCookieServiceTest.java | 6 +-
.../csrf/SkipReplicatedCsrfFilterTest.java | 7 +-
.../StandardCookieCsrfTokenRepositoryTest.java | 3 +-
.../resolver/StandardBearerTokenResolverTest.java | 7 +-
.../security/log/AuthenticationUserFilterTest.java | 8 +-
.../web/OidcBearerTokenRefreshFilterTest.java | 4 +-
...StandardAuthorizationRequestRepositoryTest.java | 3 +-
...dardOAuth2AuthorizationRequestResolverTest.java | 3 +-
.../oidc/logout/OidcLogoutSuccessHandlerTest.java | 2 +-
.../OidcAuthenticationSuccessHandlerTest.java | 4 +-
.../StandardRegistrationBuilderProviderTest.java | 11 +-
...dardRelyingPartyRegistrationRepositoryTest.java | 6 +-
...rdSaml2AuthenticationRequestRepositoryTest.java | 3 +-
.../Saml2AuthenticationSuccessHandlerTest.java | 4 +-
.../logout/Saml2LocalLogoutFilterTest.java | 2 +-
.../logout/Saml2LogoutSuccessHandlerTest.java | 2 +-
.../logout/Saml2SingleLogoutFilterTest.java | 4 +-
.../StandardSaml2LogoutRequestRepositoryTest.java | 2 +-
.../x509/X509AuthenticationProviderTest.java | 17 +-
.../nifi-framework/nifi-web/pom.xml | 2 +-
nifi-framework-bundle/nifi-framework/pom.xml | 2 +-
nifi-framework-bundle/nifi-jetty-nar/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
nifi-framework-bundle/nifi-server-nar-bom/pom.xml | 4 +-
nifi-framework-bundle/nifi-server-nar/pom.xml | 8 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
nifi-framework-bundle/pom.xml | 4 +-
nifi-frontend/pom.xml | 2 +-
.../flowfile-table/flowfile-table.component.html | 2 +-
.../bulletins-tip/bulletins-tip.component.html | 4 +-
.../codemirror-nifi-language.service.ts | 2 +-
nifi-frontend/src/main/frontend/package-lock.json | 2291 +++--
nifi-frontend/src/main/frontend/package.json | 18 +-
nifi-frontend/src/main/resources/META-INF/NOTICE | 2 +-
.../nifi-extension-manifest-model/pom.xml | 2 +-
.../nifi/extension/manifest/AllowableValue.java | 3 -
.../apache/nifi/extension/manifest/Attribute.java | 1 -
.../apache/nifi/extension/manifest/BuildInfo.java | 1 -
.../nifi/extension/manifest/ConfigurationStep.java | 2 +-
.../manifest/ConfigurationStepDependency.java | 2 +-
.../nifi/extension/manifest/ConnectorProperty.java | 2 +-
.../manifest/ConnectorPropertyDependency.java | 2 +-
.../extension/manifest/ConnectorPropertyGroup.java | 2 +-
.../manifest/ControllerServiceDefinition.java | 10 +-
.../nifi/extension/manifest/DefaultSchedule.java | 1 -
.../nifi/extension/manifest/DefaultSettings.java | 1 -
.../apache/nifi/extension/manifest/Dependency.java | 1 -
.../nifi/extension/manifest/DependentValues.java | 2 +-
.../nifi/extension/manifest/DeprecationNotice.java | 2 +-
.../nifi/extension/manifest/DynamicProperty.java | 1 -
.../extension/manifest/DynamicRelationship.java | 3 -
.../apache/nifi/extension/manifest/Extension.java | 10 +-
.../nifi/extension/manifest/ExtensionManifest.java | 2 +-
.../extension/manifest/MultiProcessorUseCase.java | 1 +
.../apache/nifi/extension/manifest/ParentNar.java | 1 -
.../apache/nifi/extension/manifest/Property.java | 2 +-
.../extension/manifest/ProvidedServiceAPI.java | 10 +-
.../nifi/extension/manifest/Relationship.java | 1 -
.../extension/manifest/ResourceDefinition.java | 2 +-
.../apache/nifi/extension/manifest/Restricted.java | 2 +-
.../nifi/extension/manifest/Restriction.java | 10 +-
.../apache/nifi/extension/manifest/Stateful.java | 2 +-
.../manifest/SystemResourceConsideration.java | 1 -
.../apache/nifi/extension/manifest/UseCase.java | 1 +
.../nifi-extension-manifest-parser/pom.xml | 4 +-
.../parser/jaxb/JAXBExtensionManifestParser.java | 10 +-
nifi-manifest/nifi-runtime-manifest-core/pom.xml | 8 +-
.../impl/StandardRuntimeManifestBuilder.java | 2 +-
nifi-manifest/nifi-runtime-manifest-test/pom.xml | 6 +-
nifi-manifest/nifi-runtime-manifest/pom.xml | 4 +-
nifi-manifest/pom.xml | 2 +-
.../nifi-processor-bundle-archetype/pom.xml | 2 +-
.../nifi-service-bundle-archetype/pom.xml | 2 +-
nifi-maven-archetypes/pom.xml | 2 +-
nifi-mock/pom.xml | 6 +-
.../java/org/apache/nifi/state/MockStateMap.java | 3 +-
.../nifi/util/ControllerServiceConfiguration.java | 5 +-
.../main/java/org/apache/nifi/util/LogMessage.java | 2 +-
...MockControllerServiceInitializationContext.java | 10 +
.../nifi/util/MockControllerServiceLookup.java | 7 +-
.../java/org/apache/nifi/util/MockEventAccess.java | 14 +-
.../java/org/apache/nifi/util/MockFlowFile.java | 10 +-
.../org/apache/nifi/util/MockFlowFileQueue.java | 4 +-
.../org/apache/nifi/util/MockKerberosContext.java | 3 +-
.../org/apache/nifi/util/MockParameterLookup.java | 1 -
.../org/apache/nifi/util/MockProcessContext.java | 28 +-
.../org/apache/nifi/util/MockProcessSession.java | 39 +-
.../util/MockProcessorInitializationContext.java | 7 +-
.../org/apache/nifi/util/MockPropertyContext.java | 5 +-
.../org/apache/nifi/util/MockPropertyValue.java | 18 +-
.../org/apache/nifi/util/MockReportingContext.java | 13 +-
.../util/MockReportingInitializationContext.java | 9 +-
.../java/org/apache/nifi/util/ReflectionUtils.java | 7 +-
.../org/apache/nifi/util/SharedSessionState.java | 7 +-
.../nifi/util/StandardProcessorTestRunner.java | 1 +
.../main/java/org/apache/nifi/util/TestRunner.java | 4 +-
.../apache/nifi/util/TestMockParameterLookup.java | 1 -
.../apache/nifi/util/TestMockProcessContext.java | 2 +-
.../apache/nifi/util/TestMockPropertyValue.java | 50 +-
.../nifi/util/TestStandardProcessorTestRunner.java | 29 +
nifi-registry/nifi-registry-assembly/LICENSE | 30 -
nifi-registry/nifi-registry-assembly/NOTICE | 4 +-
nifi-registry/nifi-registry-assembly/pom.xml | 6 +-
.../nifi-registry-bootstrap/pom.xml | 2 +-
.../nifi/registry/bootstrap/BootstrapCodec.java | 4 +-
.../exception/InvalidCommandException.java | 1 -
.../nifi-registry-bundle-utils/pom.xml | 6 +-
.../minificpp/MiNiFiCppBundleExtractor.java | 2 +-
.../bundle/extract/nar/NarBundleExtractor.java | 2 +-
.../nifi/registry/bundle/model/BundleDetails.java | 3 +-
.../bundle/extract/nar/TestNarBundleExtractor.java | 4 +-
.../nifi-registry-client/pom.xml | 12 +-
.../registry/client/NiFiRegistryClientConfig.java | 4 +-
.../apache/nifi/registry/client/TenantsClient.java | 2 +-
.../client/impl/AbstractCRUDJerseyClient.java | 6 +-
.../registry/client/impl/AbstractJerseyClient.java | 8 +-
.../nifi/registry/client/impl/ClientUtils.java | 2 +-
.../registry/client/impl/JerseyAccessClient.java | 6 +-
.../registry/client/impl/JerseyBucketClient.java | 6 +-
.../registry/client/impl/JerseyBundleClient.java | 2 +-
.../client/impl/JerseyBundleVersionClient.java | 4 +-
.../client/impl/JerseyExtensionClient.java | 6 +-
.../client/impl/JerseyExtensionRepoClient.java | 14 +-
.../registry/client/impl/JerseyFlowClient.java | 6 +-
.../client/impl/JerseyFlowSnapshotClient.java | 6 +-
.../registry/client/impl/JerseyItemsClient.java | 2 +-
.../client/impl/JerseyNiFiRegistryClient.java | 10 +-
.../registry/client/impl/JerseyPoliciesClient.java | 2 +-
.../registry/client/impl/JerseyTenantsClient.java | 2 +-
.../registry/client/impl/JerseyUserClient.java | 2 +-
.../impl/request/ProxiedEntityRequestConfig.java | 2 +-
.../nifi-registry-data-model/pom.xml | 6 +-
.../apache/nifi/registry/about/RegistryAbout.java | 1 -
.../nifi/registry/authorization/Permissions.java | 16 +-
.../authorization/ResourcePermissions.java | 17 +-
.../apache/nifi/registry/authorization/Tenant.java | 6 +-
.../apache/nifi/registry/authorization/User.java | 1 +
.../org/apache/nifi/registry/bucket/Bucket.java | 3 +-
.../apache/nifi/registry/bucket/BucketItem.java | 3 +-
.../registry/diff/ComponentDifferenceGroup.java | 8 +-
.../registry/extension/bundle/BundleVersion.java | 5 +-
.../extension/bundle/BundleVersionDependency.java | 2 +-
.../extension/bundle/BundleVersionMetadata.java | 4 +-
.../extension/component/ExtensionMetadata.java | 8 +-
.../registry/extension/component/TagCount.java | 8 +-
.../extension/repo/ExtensionRepoArtifact.java | 2 +-
.../extension/repo/ExtensionRepoBucket.java | 2 +-
.../extension/repo/ExtensionRepoGroup.java | 2 +-
.../repo/ExtensionRepoVersionSummary.java | 2 +-
.../nifi/registry/flow/VersionedFlowSnapshot.java | 7 +-
.../flow/VersionedFlowSnapshotMetadata.java | 4 +-
.../org/apache/nifi/registry/link/JaxbLink.java | 10 +-
.../org/apache/nifi/registry/link/LinkAdapter.java | 3 +-
.../nifi-registry-docker/pom.xml | 2 +-
.../nifi-registry-core/nifi-registry-docs/NOTICE | 2 +-
.../nifi-registry-core/nifi-registry-docs/pom.xml | 2 +-
.../nifi-registry-flow-diff/pom.xml | 2 +-
.../registry/flow/diff/ComparableDataFlow.java | 2 +-
.../nifi/registry/flow/diff/FlowComparator.java | 4 +-
.../nifi/registry/flow/diff/FlowDifference.java | 4 +-
.../flow/diff/StandardComparableDataFlow.java | 2 +-
.../registry/flow/diff/StandardFlowComparator.java | 18 +-
.../flow/diff/TestStandardFlowComparator.java | 298 +-
.../nifi-registry-framework/pom.xml | 28 +-
.../registry/db/CustomFlywayConfiguration.java | 4 +-
.../registry/db/CustomFlywayMigrationStrategy.java | 4 +-
.../nifi/registry/db/entity/ExtensionEntity.java | 2 +-
.../entity/ExtensionProvidedServiceApiEntity.java | 8 +-
.../db/entity/ExtensionRestrictionEntity.java | 8 +-
.../registry/db/mapper/BucketEntityRowMapper.java | 6 +-
.../db/mapper/BucketItemEntityRowMapper.java | 2 +-
.../db/mapper/ExtensionEntityRowMapper.java | 4 +-
.../registry/db/mapper/FlowEntityRowMapper.java | 2 +-
.../db/mapper/FlowSnapshotEntityRowMapper.java | 2 +-
.../registry/db/mapper/KeyEntityRowMapper.java | 2 +-
.../db/migration/LegacyDataSourceFactory.java | 2 +-
.../db/migration/LegacyDatabaseService.java | 2 +-
.../apache/nifi/registry/event/EventFactory.java | 2 +-
.../apache/nifi/registry/event/EventService.java | 2 +-
.../nifi/registry/extension/ExtensionManager.java | 6 +-
.../nifi/registry/provider/ProviderFactory.java | 4 +-
.../registry/provider/StandardProviderFactory.java | 29 +-
.../extension/StandardBundleCoordinate.java | 8 +-
.../StandardBundlePersistenceContext.java | 4 +-
.../extension/StandardBundleVersionCoordinate.java | 8 +-
.../flow/DatabaseFlowPersistenceProvider.java | 2 +-
.../provider/hook/ScriptEventHookProvider.java | 12 +-
.../authentication/IdentityProviderFactory.java | 25 +-
.../AbstractPolicyBasedAuthorizer.java | 6 +-
.../security/authorization/AuthorizerFactory.java | 43 +-
.../authorization/StandardManagedAuthorizer.java | 6 +-
.../database/DatabaseAccessPolicyProvider.java | 2 +-
.../database/DatabaseUserGroupProvider.java | 2 +-
.../authorization/file/AuthorizationsHolder.java | 5 +-
.../file/FileAccessPolicyProvider.java | 28 +-
.../authorization/file/FileUserGroupProvider.java | 24 +-
.../authorization/file/UserGroupHolder.java | 5 +-
.../authorization/resource/ResourceFactory.java | 12 +-
.../authorization/resource/ResourceType.java | 2 +-
.../security/ldap/LdapIdentityProvider.java | 4 +-
.../registry/security/ldap/LdapsSocketFactory.java | 4 +-
.../ldap/tenants/LdapUserGroupProvider.java | 14 +-
.../security/ldap/tenants/TenantHolder.java | 1 -
.../jackson/SerializationContainer.java | 2 +-
.../serialization/jaxb/JAXBSerializer.java | 10 +-
.../nifi/registry/service/MetadataService.java | 2 +-
.../nifi/registry/service/RegistryService.java | 6 +-
.../extension/BundleMetadataExtractors.java | 2 +-
.../extension/docs/HtmlExtensionDocWriter.java | 21 +-
.../nifi/registry/service/mapper/FlowMappings.java | 2 +-
.../registry/GenerateExtensionManifestSchema.java | 8 +-
.../registry/db/TestDatabaseMetadataService.java | 18 +-
.../nifi/registry/event/TestEventFactory.java | 18 +-
.../provider/MockFlowPersistenceProvider.java | 2 +-
.../provider/TestStandardProviderFactory.java | 2 +-
.../flow/TestDatabaseFlowPersistenceProvider.java | 2 +-
.../flow/git/TestGitFlowPersistenceProvider.java | 19 +-
.../provider/hook/TestScriptEventHookProvider.java | 3 +-
.../database/TestDatabaseAccessPolicyProvider.java | 2 +-
.../database/TestDatabaseUserGroupProvider.java | 2 +-
.../ldap/tenants/LdapUserGroupProviderTest.java | 142 +-
.../serialization/TestFlowContentSerializer.java | 2 +-
.../nifi/registry/service/TestRegistryService.java | 8 +-
.../src/test/resources/application.properties | 2 +-
.../nifi-registry-core/nifi-registry-jetty/pom.xml | 36 +-
.../ApplicationServerConnectorFactory.java | 2 +-
.../jetty/handler/HeaderWriterHandler.java | 3 +-
.../jetty/handler/StandardHandlerProvider.java | 12 +-
.../ApplicationServerConnectorFactoryTest.java | 2 +-
.../nifi-registry-properties-loader/pom.xml | 2 +-
.../nifi-registry-properties/pom.xml | 2 +-
.../properties/util/IdentityMappingUtil.java | 2 +-
.../nifi-registry-provider-api/pom.xml | 2 +-
.../nifi-registry-resources/pom.xml | 2 +-
.../nifi-registry-revision-api/pom.xml | 2 +-
.../nifi/registry/revision/api/Revision.java | 2 +-
.../registry/revision/api/RevisionManager.java | 2 +-
.../nifi-registry-revision-common/pom.xml | 4 +-
.../registry/revision/web/ClientIdParameter.java | 2 +-
.../nifi-registry-revision-entity-model/pom.xml | 2 +-
.../nifi-registry-revision-entity-service/pom.xml | 6 +-
.../nifi-registry-revision-spring-jdbc/pom.xml | 11 +-
.../org/apache/nifi/registry/TestApplication.java | 2 +-
.../revision/jdbc/TestJdbcRevisionManager.java | 2 +-
.../src/test/resources/application.properties | 2 +-
.../nifi-registry-revision/pom.xml | 2 +-
.../nifi-registry-runtime/pom.xml | 4 +-
.../nifi-registry-security-api/pom.xml | 2 +-
.../authentication/AuthenticationRequest.java | 8 +-
.../authentication/AuthenticationResponse.java | 20 +-
.../authentication/BasicAuthIdentityProvider.java | 3 +-
.../authentication/BearerAuthIdentityProvider.java | 3 +-
.../security/authentication/IdentityProvider.java | 3 +-
.../nifi-registry-security-utils/pom.xml | 2 +-
.../util/kerberos/KerberosPrincipalParser.java | 2 +-
.../nifi-registry-core/nifi-registry-test/pom.xml | 11 +-
.../registry/db/DatabaseProfileValueSource.java | 30 +-
.../nifi/registry/db/MariaDBDataSourceFactory.java | 3 +-
.../nifi/registry/db/MySqlDataSourceFactory.java | 3 +-
.../registry/db/Postgres11DataSourceFactory.java | 5 +-
.../registry/db/Postgres12DataSourceFactory.java | 5 +-
.../registry/db/Postgres13DataSourceFactory.java | 5 +-
.../nifi-registry-core/nifi-registry-utils/pom.xml | 2 +-
.../org/apache/nifi/registry/util/DataUnit.java | 300 +-
.../org/apache/nifi/registry/util/FileUtils.java | 7 +-
.../apache/nifi/registry/util/TestFileUtils.java | 2 +-
.../nifi-registry-web-api/pom.xml | 38 +-
.../nifi/registry/NiFiRegistryApiApplication.java | 7 +-
.../registry/NiFiRegistryPropertiesFactory.java | 3 +-
.../registry/web/NiFiRegistryResourceConfig.java | 9 +-
.../registry/web/api/AccessPolicyResource.java | 28 +-
.../nifi/registry/web/api/AccessResource.java | 4 +-
.../nifi/registry/web/api/ApplicationResource.java | 15 +-
.../registry/web/api/BucketBundleResource.java | 8 +-
.../nifi/registry/web/api/BucketFlowResource.java | 52 +-
.../nifi/registry/web/api/BucketResource.java | 16 +-
.../nifi/registry/web/api/BundleResource.java | 40 +-
.../nifi/registry/web/api/ConfigResource.java | 4 +-
.../registry/web/api/ExtensionRepoResource.java | 40 +-
.../apache/nifi/registry/web/api/FlowResource.java | 20 +-
.../apache/nifi/registry/web/api/ItemResource.java | 4 +-
.../nifi/registry/web/api/TenantResource.java | 40 +-
.../web/mapper/AccessDeniedExceptionMapper.java | 9 +-
.../web/mapper/AdministrationExceptionMapper.java | 7 +-
...ticationCredentialsNotFoundExceptionMapper.java | 7 +-
.../mapper/AuthorizationAccessExceptionMapper.java | 7 +-
.../web/mapper/BadRequestExceptionMapper.java | 11 +-
.../mapper/ConstraintViolationExceptionMapper.java | 9 +-
.../web/mapper/IllegalStateExceptionMapper.java | 7 +-
.../InvalidAuthenticationExceptionMapper.java | 7 +-
.../web/mapper/InvalidRevisionExceptionMapper.java | 9 +-
.../web/mapper/NiFiRegistryJsonProvider.java | 8 +-
.../web/mapper/NotAllowedExceptionMapper.java | 9 +-
.../web/mapper/NotFoundExceptionMapper.java | 1 -
.../web/mapper/QueryParamExceptionMapper.java | 7 +-
.../mapper/ResourceNotFoundExceptionMapper.java | 7 +-
.../web/mapper/SerializationExceptionMapper.java | 7 +-
.../web/mapper/UnauthorizedExceptionMapper.java | 7 +-
.../web/mapper/UntrustedProxyExceptionMapper.java | 7 +-
.../web/security/NiFiRegistrySecurityConfig.java | 9 +-
.../authentication/AnonymousIdentityFilter.java | 3 +-
.../security/authentication/IdentityFilter.java | 51 +-
.../kerberos/KerberosSpnegoIdentityProvider.java | 4 +-
.../oidc/StandardOidcIdentityProvider.java | 48 +-
.../web/security/authentication/util/CacheKey.java | 4 -
.../x509/X509CertificateExtractor.java | 2 +-
.../x509/X509IdentityAuthenticationProvider.java | 2 +-
.../authentication/x509/X509IdentityProvider.java | 2 +-
.../authorization/ResourceAuthorizationFilter.java | 14 +-
.../StandardHttpMethodAuthorizationRules.java | 2 +-
.../web/service/ExportedVersionedFlowSnapshot.java | 1 -
.../src/main/resources/META-INF/NOTICE | 27 +-
.../registry/NiFiRegistryTestApiApplication.java | 10 +-
.../apache/nifi/registry/web/api/BucketsIT.java | 11 +-
.../org/apache/nifi/registry/web/api/FlowsIT.java | 16 +-
.../nifi/registry/web/api/IntegrationTestBase.java | 20 +-
.../registry/web/api/IntegrationTestUtils.java | 4 +-
.../nifi/registry/web/api/NoRevisionsIT.java | 7 +-
.../apache/nifi/registry/web/api/SecureFileIT.java | 6 +-
.../nifi/registry/web/api/SecureKerberosIT.java | 4 +-
.../nifi/registry/web/api/TenantResourceTest.java | 2 +-
.../authentication/oidc/OidcServiceTest.java | 2 +-
.../oidc/StandardOidcIdentityProviderTest.java | 2 +-
.../application-ITDBFlowStorage.properties | 2 +-
.../application-ITSecureDatabase.properties | 21 +-
.../resources/application-ITSecureFile.properties | 21 +-
.../application-ITSecureKerberos.properties | 21 +-
.../resources/application-ITSecureLdap.properties | 23 +-
.../resources/application-ITSecureProxy.properties | 21 +-
.../src/test/resources/application.properties | 2 +-
.../conf/db-flow-storage/nifi-registry.properties | 2 +-
.../conf/secure-database/nifi-registry.properties | 2 +-
.../conf/secure-file/nifi-registry.properties | 2 +-
.../conf/secure-kerberos/nifi-registry.properties | 2 +-
.../conf/secure-ldap/nifi-registry.properties | 2 +-
.../conf/secure-proxy/nifi-registry.properties | 2 +-
.../nifi-registry.properties | 2 +-
.../conf/unsecured/nifi-registry.properties | 2 +-
.../nifi-registry-web-docs/pom.xml | 2 +-
.../src/main/resources/META-INF/NOTICE | 4 +-
.../nifi-registry-web-ui/pom.xml | 2 +-
.../nifi/registry/web/filter/LoginFilter.java | 1 +
.../src/main/resources/META-INF/NOTICE | 2 +-
nifi-registry/nifi-registry-core/pom.xml | 22 +-
.../nifi-registry-docker-maven/dockermaven/pom.xml | 2 +-
nifi-registry/nifi-registry-docker-maven/pom.xml | 2 +-
.../nifi-registry-aws-assembly/NOTICE | 2 +-
.../nifi-registry-aws-assembly/pom.xml | 4 +-
.../nifi-registry-aws-extensions/pom.xml | 2 +-
.../nifi-registry-aws/pom.xml | 2 +-
nifi-registry/nifi-registry-extensions/pom.xml | 2 +-
.../nifi-registry-toolkit-assembly/NOTICE | 55 +-
.../nifi-registry-toolkit-assembly/pom.xml | 4 +-
.../src/main/resources/classpath/log4j.properties | 2 +-
.../nifi-registry-toolkit-persistence/pom.xml | 4 +-
.../FlowPersistenceProviderMigrator.java | 5 +-
.../FlowPersistenceProviderMigratorTest.java | 5 +-
nifi-registry/nifi-registry-toolkit/pom.xml | 2 +-
nifi-registry/pom.xml | 81 +-
nifi-server-api/pom.xml | 6 +-
nifi-stateless/nifi-stateless-api/pom.xml | 2 +-
.../stateless/engine/DataflowAbortedException.java | 1 -
.../nifi/stateless/engine/NarUnpackLock.java | 5 +-
.../engine/StatelessEngineConfiguration.java | 2 +-
...ropertiesFileEngineConfigurationParserTest.java | 2 +-
nifi-stateless/nifi-stateless-assembly/NOTICE | 2 +-
nifi-stateless/nifi-stateless-assembly/pom.xml | 10 +-
nifi-stateless/nifi-stateless-bootstrap/pom.xml | 2 +-
.../bootstrap/BootstrapConfiguration.java | 2 +-
.../src/main/resources/nifi-stateless.properties | 2 +-
.../src/test/resources/nifi-stateless.properties | 2 +-
.../nifi-stateless-engine/pom.xml | 32 +-
.../extensions/FileSystemExtensionRepository.java | 2 +-
.../config/PropertiesFileFlowDefinitionParser.java | 2 +-
.../stateless/config/SslConfigurationUtil.java | 10 +-
.../core/RegistryFlowSnapshotProvider.java | 7 +-
.../nifi/stateless/engine/ComponentBuilder.java | 15 +-
.../nifi/stateless/engine/StatelessEngine.java | 2 +-
.../stateless/engine/StatelessReloadComponent.java | 2 +-
.../stateless/engine/StatelessSchedulingAgent.java | 2 +-
.../repository/ByteArrayContentRepository.java | 13 +-
.../StatelessFileSystemContentRepository.java | 25 +-
.../test/resources/flow-configuration.properties | 2 +-
.../nifi-stateless-nar/pom.xml | 4 +-
.../src/main/resources/META-INF/NOTICE | 2 +-
nifi-stateless/nifi-stateless-bundle/pom.xml | 2 +-
nifi-stateless/nifi-stateless-resources/pom.xml | 2 +-
nifi-stateless/pom.xml | 2 +-
.../nifi-alternate-config-extensions-nar/pom.xml | 6 +-
.../nifi-alternate-config-extensions/pom.xml | 4 +-
.../pom.xml | 2 +-
.../nifi-python-test-extensions-nar/pom.xml | 4 +-
.../nifi-stateless-system-test-suite/pom.xml | 36 +-
.../apache/nifi/stateless/StatelessSystemIT.java | 4 +-
.../nifi/stateless/VersionedFlowBuilder.java | 14 +-
.../stateless/basics/CancelFlowExecutionIT.java | 2 +-
.../nifi/stateless/basics/CloneFlowFileIT.java | 4 +-
.../nifi/stateless/basics/CreatesFlowFileIT.java | 2 +-
.../nifi/stateless/basics/InputOutputIT.java | 4 +-
.../apache/nifi/stateless/basics/LoopingIT.java | 2 +-
.../apache/nifi/stateless/basics/SplittingIT.java | 2 +-
.../classloader/InstanceClassLoaderIT.java | 2 +-
.../nifi-system-test-authorizer-nar/pom.xml | 4 +-
.../nifi-system-test-authorizer/pom.xml | 4 +-
.../nifi-system-test-authorizer-bundle/pom.xml | 2 +-
.../nifi-system-test-extensions-nar/pom.xml | 8 +-
.../pom.xml | 6 +-
.../pom.xml | 2 +-
.../pom.xml | 6 +-
.../nifi-system-test-extensions-services/pom.xml | 8 +-
...derService.java => ModifyClasspathService.java} | 44 +-
.../org.apache.nifi.controller.ControllerService | 1 +
.../nifi-system-test-extensions/pom.xml | 10 +-
.../tests/system/CalculateConnector.java | 2 +-
.../tests/system/DataQueuingConnector.java | 2 +-
...ntrollerServiceReferencingFlowAnalysisRule.java | 2 +-
.../tests/system/PropertiesParameterProvider.java | 2 +-
.../tests/system/DependOnProperties.java | 5 +-
.../tests/system/GenerateAndCountCallbacks.java | 20 +-
.../nifi-system-test-extensions-bundle/pom.xml | 2 +-
.../nifi-system-test-extensions2-nar/pom.xml | 8 +-
.../nifi-system-test-extensions2/pom.xml | 4 +-
.../nifi-system-test-extensions2-bundle/pom.xml | 2 +-
.../pom.xml | 4 +-
.../nifi-system-test-flow-action-reporter/pom.xml | 4 +-
.../pom.xml | 2 +-
.../nifi-nar-provider-assembly/pom.xml | 8 +-
.../nifi-nar-provider-processors-nar/pom.xml | 8 +-
.../nifi-nar-provider-processors/pom.xml | 8 +-
.../nifi-nar-provider-service-api-nar/pom.xml | 8 +-
.../nifi-nar-provider-service-api/pom.xml | 2 +-
.../nifi-nar-provider-service-nar/pom.xml | 8 +-
.../nifi-nar-provider-service/pom.xml | 8 +-
.../nifi-system-test-nar-provider-bundles/pom.xml | 2 +-
nifi-system-tests/nifi-system-test-suite/pom.xml | 60 +-
.../nifi/tests/system/AggregateNiFiInstance.java | 2 +-
.../org/apache/nifi/tests/system/NiFiInstance.java | 2 +-
.../nifi/tests/system/NiFiInstanceCache.java | 2 +-
.../tests/system/NiFiSystemKeyStoreProvider.java | 6 +-
.../SpawnedStandaloneNiFiInstanceFactory.java | 70 +-
.../clustering/JoinClusterWithDifferentFlowIT.java | 2 +-
...JoinClusterWithMissingConnectionWithDataIT.java | 2 +-
.../ControllerServiceDisableWithReferencesIT.java | 136 +
.../nifi/tests/system/nar/NarUploadPythonIT.java | 2 +-
.../parameters/ClusteredParameterContextIT.java | 91 +
.../apache/nifi/tests/system/pg/CopyPasteIT.java | 4 +-
.../tests/system/python/PythonProcessorIT.java | 28 +-
.../tests/system/state/ClusterStateKeyDropIT.java | 2 +-
.../system/state/StandaloneStateKeyDropIT.java | 2 +-
.../tests/system/stateless/StatelessBasicsIT.java | 4 +-
nifi-system-tests/pom.xml | 2 +-
nifi-toolkit/nifi-toolkit-api/pom.xml | 2 +-
nifi-toolkit/nifi-toolkit-assembly/NOTICE | 2 +-
nifi-toolkit/nifi-toolkit-assembly/pom.xml | 4 +-
.../main/resources/classpath/overlay.properties | 2 +-
nifi-toolkit/nifi-toolkit-cli/pom.xml | 16 +-
.../nifi/toolkit/cli/api/ResolvedReference.java | 4 +-
.../cli/impl/client/NiFiRegistryClientFactory.java | 3 +-
.../toolkit/cli/impl/command/nifi/pg/PGDelete.java | 2 +-
.../toolkit/cli/impl/command/nifi/pg/PGImport.java | 2 +-
.../toolkit/cli/impl/command/nifi/pg/PGRename.java | 2 +-
.../command/nifi/pg/cs/ControllerServiceUtil.java | 28 +-
.../command/registry/NiFiRegistryCommandGroup.java | 2 +-
.../cli/impl/result/nifi/ChangeVersionResult.java | 2 +-
.../cli/impl/result/nifi/ProcessorsResult.java | 2 +-
.../apache/nifi/toolkit/cli/TestCLICompleter.java | 3 -
.../toolkit/cli/impl/result/TestBucketsResult.java | 22 +-
.../TestRegisteredFlowSnapshotMetadataResult.java | 9 +-
.../cli/impl/result/TestRegistryClientResult.java | 9 +-
.../cli/impl/result/TestVersionedFlowsResult.java | 25 +-
.../toolkit/cli/impl/result/util/OutputUtil.java | 22 +-
.../impl/result/writer/TestDynamicTableWriter.java | 59 +-
nifi-toolkit/nifi-toolkit-client/pom.xml | 14 +-
.../nifi/toolkit/client/NiFiClientConfig.java | 5 +-
.../nifi/toolkit/client/impl/JerseyNiFiClient.java | 3 +-
.../client/impl/JerseyProcessGroupClient.java | 2 +-
.../OIDCClientCredentialsRequestConfig.java | 5 +-
.../nifi/toolkit/client/TestProcessGroupBox.java | 4 +-
nifi-toolkit/pom.xml | 2 +-
pom.xml | 102 +-
3861 files changed, 33740 insertions(+), 35822 deletions(-)
create mode 100755 .github/scripts/collect-system-tests-coverage.sh
rename nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaTeamFetcher.java => nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/DateTimeTestUtil.java (64%)
create mode 100644 nifi-commons/nifi-record/src/test/java/org/apache/nifi/serialization/record/field/ObjectLocalTimeFieldConverterTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors-nar/pom.xml
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors-nar/src/main/resources/META-INF/LICENSE
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors-nar/src/main/resources/META-INF/NOTICE
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/pom.xml
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/AsanaObjectType.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/GetAsanaObject.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/MapStringSerDe.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/StringSerDe.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AbstractAsanaObjectFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaObject.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaObjectFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaObjectFetcherException.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaObjectState.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaProjectEventFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaProjectFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaProjectMembershipFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaProjectStatusAttachmentFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaProjectStatusFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaStoryFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaTagFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaTaskAttachmentFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaTaskFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaTeamMemberFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/AsanaUserFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/java/org/apache/nifi/processors/asana/utils/GenericAsanaObjectFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/main/resources/docs/org.apache.nifi.processors.asana.GetAsanaObject/additionalDetails.md
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AbstractAsanaObjectFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaObjectTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaProjectEventFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaProjectFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaProjectMembershipFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaProjectStatusAttachmentFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaProjectStatusFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaStoryFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaTagFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaTaskAttachmentFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaTaskFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaTeamFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaTeamMemberFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/AsanaUserFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/GenericAsanaObjectFetcherTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/GetAsanaObjectConfigurationTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/GetAsanaObjectLifecycleTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/MapStringSerDeTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/StringSerDeTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/mocks/MockAbstractAsanaObjectFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/mocks/MockAsanaClientProviderService.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/mocks/MockDistributedMapCacheClient.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/mocks/MockGenericAsanaObjectFetcher.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-processors/src/test/java/org/apache/nifi/processors/asana/mocks/MockGetAsanaObject.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-api-nar/pom.xml
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-api-nar/src/main/resources/META-INF/LICENSE
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-api-nar/src/main/resources/META-INF/NOTICE
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-api/pom.xml
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-api/src/main/java/org/apache/nifi/controller/asana/AsanaClient.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-api/src/main/java/org/apache/nifi/controller/asana/AsanaClientException.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-api/src/main/java/org/apache/nifi/controller/asana/AsanaClientProviderService.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-api/src/main/java/org/apache/nifi/controller/asana/AsanaEventsCollection.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-nar/pom.xml
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-nar/src/main/resources/META-INF/LICENSE
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services-nar/src/main/resources/META-INF/NOTICE
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services/pom.xml
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services/src/main/java/org/apache/nifi/controller/asana/StandardAsanaClient.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services/src/main/java/org/apache/nifi/controller/asana/StandardAsanaClientProviderService.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services/src/main/resources/docs/org.apache.nifi.controller.asana.AsanaClientService/additionalDetails.md
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/nifi-asana-services/src/test/java/org/apache/nifi/controller/asana/StandardAsanaClientProviderServiceTest.java
delete mode 100644 nifi-extension-bundles/nifi-asana-bundle/pom.xml
create mode 100644 nifi-extension-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/encryption/S3EncryptionCommitmentPolicy.java
copy nifi-extension-bundles/{nifi-extension-utils/nifi-database-utils/src/main/java/org/apache/nifi/util/db/SensitiveValueWrapper.java => nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/ObsoleteAbstractAwsProcessorProperties.java} (66%)
create mode 100644 nifi-extension-bundles/nifi-dropbox-bundle/nifi-dropbox-processors/src/test/java/org/apache/nifi/processors/dropbox/TestDropboxProcessorMigration.java
create mode 100644 nifi-extension-bundles/nifi-dropbox-bundle/nifi-dropbox-services/src/test/java/org/apache/nifi/services/dropbox/StandardDropboxCredentialServiceTest.java
create mode 100644 nifi-extension-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-client-service/src/test/java/org/apache/nifi/elasticsearch/TestElasticSearchControllerServiceMigration.java
create mode 100644 nifi-extension-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-restapi-processors/src/test/java/org/apache/nifi/processors/elasticsearch/TestElasticSearchProcessorMigration.java
create mode 100644 nifi-extension-bundles/nifi-extension-utils/nifi-database-utils/src/test/java/org/apache/nifi/util/db/AbstractConnectionTest.java
copy nifi-extension-bundles/nifi-extension-utils/{nifi-event-put => nifi-embedded-database-service}/pom.xml (75%)
rename nifi-extension-bundles/{nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard => nifi-extension-utils/nifi-embedded-database-service/src/main/java/org/apache/nifi/embedded/database}/EmbeddedDatabaseConnectionService.java (55%)
delete mode 100644 nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/TestJoltTransformJson/TestCustomJoltTransform.jar
delete mode 100644 nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/TestJoltTransformRecord/TestCustomJoltTransform.jar
create mode 100644 nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-utils/src/test/java/org/apache/nifi/processors/jolt/CustomTransformJarProvider.java
rename nifi-extension-bundles/nifi-jolt-bundle/{nifi-jolt-processors/src/test/java => nifi-jolt-utils/src/test/java/org/apache/nifi/processors/jolt}/TestCustomJoltTransform.java (73%)
delete mode 100644 nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-utils/src/test/resources/TestTransformFactory/TestCustomJoltTransform.jar
create mode 100644 nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/KafkaConnectionVerifier.java
create mode 100644 nifi-extension-bundles/nifi-salesforce-bundle/nifi-salesforce-processors/src/test/java/org/apache/nifi/processors/salesforce/QuerySalesforceObjectTest.java
create mode 100644 nifi-extension-bundles/nifi-salesforce-bundle/nifi-salesforce-processors/src/test/resources/query/describe_sobject.json
create mode 100644 nifi-extension-bundles/nifi-salesforce-bundle/nifi-salesforce-processors/src/test/resources/query/query_sobject.json
create mode 100644 nifi-extension-bundles/nifi-standard-bundle/nifi-standard-content-viewer/src/test/java/org/apache/nifi/web/controller/StandardContentViewerControllerTest.java
delete mode 100644 nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/db/impl/DerbyDatabaseAdapter.java
delete mode 100644 nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/META-INF/services/org.apache.nifi.processors.standard.db.DatabaseAdapter
create mode 100644 nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestForkRecord/input/complex-input-json-for-inference.json
copy nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestForkRecord/output/{split-address.json => extract-address-with-parents.json} (65%)
create mode 100644 nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestForkRecord/output/extract-address-without-parents.json
create mode 100644 nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/resources/TestForkRecord/output/extract-bank-accounts-with-parents.json
create mode 100644 nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/resources/snapshots/migrated-property/web-client-service-provider.json
copy nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/{ApplicationPropertiesConfiguration.java => TaskSchedulerConfiguration.java} (58%)
create mode 100644 nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java
delete mode 100644 nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ReflectionUtils.java
rename nifi-framework-bundle/nifi-framework/{nifi-framework-components => nifi-nar-utils}/src/main/java/org/apache/nifi/util/ReflectionUtils.java (95%)
create mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/prometheusutil/VersionInfoRegistry.java
copy nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions-services/src/main/java/org/apache/nifi/cs/tests/system/{ClassloaderIsolationKeyProviderService.java => ModifyClasspathService.java} (53%)
create mode 100644 nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/controllerservice/ControllerServiceDisableWithReferencesIT.java
copy nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/JsonUtil.java => nifi-toolkit/nifi-toolkit-cli/src/test/java/org/apache/nifi/toolkit/cli/impl/result/util/OutputUtil.java (68%)
(nifi) 15/40: NIFI-15315 Add support for assets in connectors (#10647)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit dd971a1b2709e628073268f9f4bc167f487b2182
Author: Bryan Bende <bb...@apache.org>
AuthorDate: Wed Dec 17 17:16:54 2025 -0500
NIFI-15315 Add support for assets in connectors (#10647)
- Support multiple asset references
- Clean up of assets in StandardConnectorDAO after apply and discard
- Add endpoint for listing assets for a given connector
- Add endpoint for discarding the working config of a connector
- Add endpoint for retrieving asset content
- Add system test for connectors that uploads an asset
Signed-off-by: Kevin Doran <kd...@apache.org>
* Fix issues after rebasing, enable ClusteredConnectorAssetsIT
---
.../java/org/apache/nifi/util/NiFiProperties.java | 3 +
.../java/org/apache/nifi/asset/AssetManager.java | 30 +-
.../web/api/dto/ConnectorValueReferenceDTO.java | 28 +-
.../http/endpoints/AssetsEndpointMerger.java | 2 +-
.../java/org/apache/nifi/asset/StandardAsset.java | 13 +-
.../apache/nifi/asset/StandardAssetManager.java | 103 ++++---
.../nifi/asset/StandardConnectorAssetManager.java | 225 ++++++++++++++
.../flow/mapping/VersionedComponentFlowMapper.java | 2 +-
.../services/org.apache.nifi.asset.AssetManager | 3 +-
.../asset/StandardConnectorAssetManagerTest.java | 178 +++++++++++
...nContext.java => ConnectorAssetRepository.java} | 23 +-
.../components/connector/ConnectorRepository.java | 1 +
.../ConnectorRepositoryInitializationContext.java | 3 +
.../org/apache/nifi/asset/AssetsRestApiClient.java | 55 +++-
.../nifi/asset/StandardAssetComponentManager.java | 4 +-
.../nifi/asset/StandardAssetSynchronizer.java | 4 +-
...ava => StandardConnectorAssetSynchronizer.java} | 149 ++++------
.../StandardConnectorAssetRepository.java | 63 ++++
.../StandardConnectorConfigurationContext.java | 19 +-
.../connector/StandardConnectorNode.java | 33 ++-
...StandardConnectorRepoInitializationContext.java | 9 +
.../connector/StandardConnectorRepository.java | 6 +
.../apache/nifi/controller/ExtensionBuilder.java | 54 ++--
.../org/apache/nifi/controller/FlowController.java | 57 +++-
.../nifi/controller/StandardFlowService.java | 30 +-
.../nifi/controller/flow/StandardFlowManager.java | 2 +-
.../serialization/VersionedFlowSynchronizer.java | 2 +-
.../configuration/FlowControllerConfiguration.java | 24 +-
...TestStandardConnectorInitializationContext.java | 16 +
.../apache/nifi/headless/HeadlessNiFiServer.java | 3 +-
.../org/apache/nifi/audit/ConnectorAuditor.java | 10 +-
.../org/apache/nifi/web/NiFiServiceFacade.java | 24 +-
.../apache/nifi/web/StandardNiFiServiceFacade.java | 59 +++-
.../org/apache/nifi/web/api/ConnectorResource.java | 327 ++++++++++++++++++++-
.../nifi/web/api/ParameterContextResource.java | 4 +-
.../org/apache/nifi/web/api/dto/DtoFactory.java | 22 +-
.../configuration/WebApplicationConfiguration.java | 1 +
.../java/org/apache/nifi/web/dao/ConnectorDAO.java | 16 +-
.../nifi/web/dao/impl/StandardConnectorDAO.java | 97 +++++-
.../web/dao/impl/StandardParameterContextDAO.java | 2 +-
.../web/dao/impl/StandardConnectorDAOTest.java | 25 ++
.../connectors/tests/system/AssetConnector.java | 93 ++++++
.../org.apache.nifi.components.connector.Connector | 3 +
.../connectors/ClusteredConnectorAssetsIT.java | 142 +++++++++
.../tests/system/connectors/ConnectorAssetsIT.java | 202 +++++++++++++
.../nifi/toolkit/client/ConnectorClient.java | 39 +++
.../toolkit/client/impl/JerseyConnectorClient.java | 86 ++++++
.../nifi/toolkit/client/impl/JerseyNiFiClient.java | 2 +-
48 files changed, 2006 insertions(+), 292 deletions(-)
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 2b354a7bc3..572d4d3582 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -124,6 +124,9 @@ public class NiFiProperties extends ApplicationProperties {
public static final String ASSET_MANAGER_IMPLEMENTATION = "nifi.asset.manager.implementation";
public static final String ASSET_MANAGER_PREFIX = "nifi.asset.manager.properties.";
+ public static final String CONNECTOR_ASSET_MANAGER_IMPLEMENTATION = "nifi.connector.asset.manager.implementation";
+ public static final String CONNECTOR_ASSET_MANAGER_PREFIX = "nifi.connector.asset.manager.properties.";
+
// status repository properties
public static final String COMPONENT_STATUS_REPOSITORY_IMPLEMENTATION = "nifi.components.status.repository.implementation";
public static final String COMPONENT_STATUS_SNAPSHOT_FREQUENCY = "nifi.components.status.snapshot.frequency";
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/asset/AssetManager.java b/nifi-framework-api/src/main/java/org/apache/nifi/asset/AssetManager.java
index d1501c2f93..321d0afe7b 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/asset/AssetManager.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/asset/AssetManager.java
@@ -31,13 +31,25 @@ public interface AssetManager {
/**
* Creates a new Asset with the given name and contents.
- * @param parameterContextId the id of the parameter context
+ * @param ownerId the id of the resource that this asset belongs to
* @param assetName the name of the asset
* @param contents the contents of the asset
* @return the created asset
* @throws IOException if there is an error creating the asset
*/
- Asset createAsset(String parameterContextId, String assetName, InputStream contents) throws IOException;
+ Asset createAsset(String ownerId, String assetName, InputStream contents) throws IOException;
+
+ /**
+ * Saves the given asset. If an asset already exists with the given identifier, the content will be replaced.
+ *
+ * @param ownerId the id of the resource that this asset belongs to
+ * @param assetId the identifier of the asset
+ * @param assetName the name of the asset
+ * @param contents the new contents of the asset
+ * @return the created or updated Asset
+ * @throws IOException if there is an error writing the asset
+ */
+ Asset saveAsset(String ownerId, String assetId, String assetName, InputStream contents) throws IOException;
/**
* Retrieves the Asset with the given id, if it exists.
@@ -47,22 +59,22 @@ public interface AssetManager {
Optional<Asset> getAsset(String id);
/**
- * Retrieves the Assets that belong to the given parameter context.
- * @param parameterContextId the id of the parameter context
- * @return the list of assets for the given context
+ * Retrieves the Assets that belong to the given owner.
+ * @param ownerId the id of the owner resource
+ * @return the list of assets for the given owner
*/
- List<Asset> getAssets(String parameterContextId);
+ List<Asset> getAssets(String ownerId);
/**
- * Creates an Asset with the given name and associates it with the given parameter context. If the asset already exists, it is returned. Otherwise, an asset is created
+ * Creates an Asset with the given name and associates it with the given owner. If the asset already exists, it is returned. Otherwise, an asset is created
* but the underlying file is not created. This allows the asset to be referenced but any component that attempts to use the asset will still see a File that does not exist, which
* will typically lead to an invalid component.
*
- * @param parameterContextId the id of the parameter context
+ * @param ownerId the id of the owner
* @param assetName the name of the asset
* @return the created asset
*/
- Asset createMissingAsset(String parameterContextId, String assetName);
+ Asset createMissingAsset(String ownerId, String assetName);
/**
* Deletes the Asset with the given id, if it exists.
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java
index 297ddee44c..ed79bdb3ac 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java
@@ -19,6 +19,8 @@ package org.apache.nifi.web.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.xml.bind.annotation.XmlType;
+import java.util.List;
+
/**
* A reference to a connector property value, which includes the value and its type.
* The value can be a string literal, a reference to an asset, or a reference to a secret.
@@ -28,11 +30,11 @@ public class ConnectorValueReferenceDTO {
private String valueType;
private String value;
- private String assetIdentifier;
private String secretProviderId;
private String secretProviderName;
private String secretName;
private String fullyQualifiedSecretName;
+ private List<AssetReferenceDTO> assetReferences;
/**
* @return the type of value (STRING_LITERAL, ASSET_REFERENCE, or SECRET_REFERENCE)
@@ -58,18 +60,6 @@ public class ConnectorValueReferenceDTO {
this.value = value;
}
- /**
- * @return the asset identifier when valueType is ASSET_REFERENCE
- */
- @Schema(description = "The asset identifier. Applicable when valueType is ASSET_REFERENCE.")
- public String getAssetIdentifier() {
- return assetIdentifier;
- }
-
- public void setAssetIdentifier(final String assetIdentifier) {
- this.assetIdentifier = assetIdentifier;
- }
-
/**
* @return the secret provider identifier when valueType is SECRET_REFERENCE
*/
@@ -114,5 +104,17 @@ public class ConnectorValueReferenceDTO {
public String getFullyQualifiedSecretName() {
return fullyQualifiedSecretName;
}
+
+ /**
+ * @return the asset identifiers when valueType is ASSET_REFERENCE
+ */
+ @Schema(description = "The asset references. Applicable when valueType is ASSET_REFERENCE.")
+ public List<AssetReferenceDTO> getAssetReferences() {
+ return assetReferences;
+ }
+
+ public void setAssetReferences(final List<AssetReferenceDTO> assetReferences) {
+ this.assetReferences = assetReferences;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AssetsEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AssetsEndpointMerger.java
index 422ade1492..bcf5e0e066 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AssetsEndpointMerger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AssetsEndpointMerger.java
@@ -31,7 +31,7 @@ import java.util.regex.Pattern;
public class AssetsEndpointMerger extends AbstractSingleDTOEndpoint<AssetsEntity, Collection<AssetEntity>> {
- private static final Pattern ASSETS_URI = Pattern.compile("/nifi-api/parameter-contexts/[a-f0-9\\-]{36}/assets");
+ private static final Pattern ASSETS_URI = Pattern.compile("/nifi-api/(parameter-contexts|connectors)/[a-f0-9\\-]{36}/assets");
@Override
public boolean canHandle(final URI uri, final String method) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardAsset.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardAsset.java
index 77e2ef5b64..ed0cea997b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardAsset.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardAsset.java
@@ -23,14 +23,14 @@ import java.util.Optional;
public class StandardAsset implements Asset {
private final String identifier;
- private final String parameterContextIdentifier;
+ private final String ownerIdentifier;
private final String name;
private final File file;
private final String digest;
- public StandardAsset(final String identifier, final String paramContextIdentifier, final String name, final File file, final String digest) {
+ public StandardAsset(final String identifier, final String ownerIdentifier, final String name, final File file, final String digest) {
this.identifier = Objects.requireNonNull(identifier, "Identifier is required");
- this.parameterContextIdentifier = Objects.requireNonNull(paramContextIdentifier, "Parameter Context Identifier is required");
+ this.ownerIdentifier = Objects.requireNonNull(ownerIdentifier, "Owner Identifier is required");
this.name = Objects.requireNonNull(name, "Name is required");
this.file = Objects.requireNonNull(file, "File is required");
this.digest = digest;
@@ -43,7 +43,12 @@ public class StandardAsset implements Asset {
@Override
public String getParameterContextIdentifier() {
- return parameterContextIdentifier;
+ return ownerIdentifier;
+ }
+
+ @Override
+ public String getOwnerIdentifier() {
+ return ownerIdentifier;
}
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardAssetManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardAssetManager.java
index f643cccf3a..28b9054622 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardAssetManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardAssetManager.java
@@ -68,36 +68,20 @@ public class StandardAssetManager implements AssetManager {
}
@Override
- public Asset createAsset(final String parameterContextId, final String assetName, final InputStream contents) throws IOException {
- final String assetId = createAssetId(parameterContextId, assetName);
-
- final File file = getFile(parameterContextId, assetName);
- final File dir = file.getParentFile();
- if (!dir.exists()) {
- try {
- Files.createDirectories(dir.toPath());
- } catch (final IOException ioe) {
- throw new IOException("Could not create directory in order to store asset", ioe);
- }
- }
-
- // Write contents to a temporary file, then move it to the final location.
- // This allows us to avoid a situation where we upload a file, then we attempt to overwrite it but fail, leaving a corrupt asset.
- final File tempFile = new File(dir, file.getName() + ".tmp");
- logger.debug("Writing temp asset file [{}]", tempFile.getAbsolutePath());
+ public Asset createAsset(final String ownerId, final String assetName, final InputStream contents) throws IOException {
+ final String assetId = createAssetId(ownerId, assetName);
+ final File assetFile = getFile(ownerId, assetName);
+ return saveAsset(ownerId, assetId, assetName, contents, assetFile);
+ }
- try {
- Files.copy(contents, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
- } catch (final Exception e) {
- throw new IOException("Failed to write asset to file " + tempFile.getAbsolutePath(), e);
+ @Override
+ public Asset saveAsset(final String ownerId, final String assetId, final String assetName, final InputStream contents) throws IOException {
+ final String generatedAssetId = createAssetId(ownerId, assetName);
+ if (!generatedAssetId.equals(assetId)) {
+ throw new IllegalArgumentException("The provided asset id [%s] does not match the id this asset manager generated for the given owner and name".formatted(assetId));
}
-
- Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
-
- final String digest = computeDigest(file);
- final Asset asset = new StandardAsset(assetId, parameterContextId, assetName, file, digest);
- assets.put(assetId, asset);
- return asset;
+ final File assetFile = getFile(ownerId, assetName);
+ return saveAsset(ownerId, assetId, assetName, contents, assetFile);
}
@Override
@@ -106,22 +90,22 @@ public class StandardAssetManager implements AssetManager {
}
@Override
- public List<Asset> getAssets(final String parameterContextId) {
+ public List<Asset> getAssets(final String ownerId) {
final List<Asset> allAssets = new ArrayList<>(assets.values());
- final List<Asset> paramContextAssets = new ArrayList<>();
+ final List<Asset> ownerAssets = new ArrayList<>();
for (final Asset asset : allAssets) {
- if (asset.getParameterContextIdentifier().equals(parameterContextId)) {
- paramContextAssets.add(asset);
+ if (asset.getOwnerIdentifier().equals(ownerId)) {
+ ownerAssets.add(asset);
}
}
- return paramContextAssets;
+ return ownerAssets;
}
@Override
- public Asset createMissingAsset(final String parameterContextId, final String assetName) {
- final String assetId = createAssetId(parameterContextId, assetName);
- final File file = getFile(parameterContextId, assetName);
- final Asset asset = new StandardAsset(assetId, parameterContextId, assetName, file, null);
+ public Asset createMissingAsset(final String ownerId, final String assetName) {
+ final String assetId = createAssetId(ownerId, assetName);
+ final File file = getFile(ownerId, assetName);
+ final Asset asset = new StandardAsset(assetId, ownerId, assetName, file, null);
assets.put(assetId, asset);
return asset;
}
@@ -155,14 +139,43 @@ public class StandardAssetManager implements AssetManager {
return Optional.of(removed);
}
- private String createAssetId(final String parameterContextId, final String assetName) {
- final String seed = parameterContextId + "/" + assetName;
+ private Asset saveAsset(final String ownerId, final String assetId, final String assetName, final InputStream contents, final File assetFile) throws IOException {
+ final File dir = assetFile.getParentFile();
+ if (!dir.exists()) {
+ try {
+ Files.createDirectories(dir.toPath());
+ } catch (final IOException ioe) {
+ throw new IOException("Could not create directory in order to store asset", ioe);
+ }
+ }
+
+ // Write contents to a temporary file, then move it to the final location.
+ // This allows us to avoid a situation where we upload a file, then we attempt to overwrite it but fail, leaving a corrupt asset.
+ final File tempFile = new File(dir, assetFile.getName() + ".tmp");
+ logger.debug("Writing temp asset file [{}]", tempFile.getAbsolutePath());
+
+ try {
+ Files.copy(contents, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (final Exception e) {
+ throw new IOException("Failed to write asset to file " + tempFile.getAbsolutePath(), e);
+ }
+
+ Files.move(tempFile.toPath(), assetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ final String digest = computeDigest(assetFile);
+ final Asset asset = new StandardAsset(assetId, ownerId, assetName, assetFile, digest);
+ assets.put(assetId, asset);
+ return asset;
+ }
+
+ private String createAssetId(final String ownerId, final String assetName) {
+ final String seed = ownerId + "/" + assetName;
return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
}
- private File getFile(final String paramContextId, final String assetName) {
+ private File getFile(final String ownerId, final String assetName) {
final Path parentPath = assetStorageLocation.toPath().normalize();
- final Path assetPath = Paths.get(paramContextId, assetName).normalize();
+ final Path assetPath = Paths.get(ownerId, assetName).normalize();
final Path fullPath = parentPath.resolve(assetPath);
return fullPath.toFile();
}
@@ -183,17 +196,17 @@ public class StandardAssetManager implements AssetManager {
continue;
}
- final String contextId = file.getName();
+ final String ownerId = file.getName();
final File[] assetFiles = file.listFiles();
if (assetFiles == null) {
- logger.warn("Unable to determine which assets exist for Parameter Context {}", contextId);
+ logger.warn("Unable to determine which assets exist for owner {}", ownerId);
continue;
}
for (final File assetFile : assetFiles) {
- final String assetId = createAssetId(contextId, assetFile.getName());
+ final String assetId = createAssetId(ownerId, assetFile.getName());
final String digest = computeDigest(assetFile);
- final Asset asset = new StandardAsset(assetId, contextId, assetFile.getName(), assetFile, digest);
+ final Asset asset = new StandardAsset(assetId, ownerId, assetFile.getName(), assetFile, digest);
assets.put(assetId, asset);
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardConnectorAssetManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardConnectorAssetManager.java
new file mode 100644
index 0000000000..7cc907de11
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/asset/StandardConnectorAssetManager.java
@@ -0,0 +1,225 @@
+/*
+ * 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.asset;
+
+import org.apache.nifi.nar.FileDigestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * AssetManager implementation for Connectors. This differs from the StandardAssetManager used for Parameter Contexts in that
+ * Connectors want every asset added to be treated as a new asset, even if the same filename is being saved to the same Connector.
+ * This is because all changes to the Connector needs to be stored separately in the working configuration until applied and can
+ * not interfere with the active configuration.
+ */
+public class StandardConnectorAssetManager implements AssetManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(StandardConnectorAssetManager.class);
+
+ public static final String ASSET_STORAGE_LOCATION_PROPERTY = "directory";
+ public static final String DEFAULT_ASSET_STORAGE_LOCATION = "./connector-assets";
+
+ private volatile File assetStorageLocation;
+ private final Map<String, Asset> assets = new ConcurrentHashMap<>();
+
+ @Override
+ public void initialize(final AssetManagerInitializationContext context) {
+ final String storageLocation = getStorageLocation(context);
+
+ assetStorageLocation = new File(storageLocation);
+ if (!assetStorageLocation.exists()) {
+ try {
+ Files.createDirectories(assetStorageLocation.toPath());
+ } catch (IOException e) {
+ throw new RuntimeException("The Connector Asset Manager's [%s] property is set to [%s] but the directory does not exist and cannot be created"
+ .formatted(ASSET_STORAGE_LOCATION_PROPERTY, storageLocation), e);
+ }
+ }
+
+ try {
+ recoverLocalAssets();
+ } catch (final IOException e) {
+ throw new RuntimeException("Unable to access connector assets", e);
+ }
+ }
+
+ @Override
+ public Asset createAsset(final String connectorId, final String assetName, final InputStream contents) throws IOException {
+ final String assetId = generateAssetId();
+ final File assetFile = getFile(connectorId, assetId, assetName);
+ return saveAsset(connectorId, assetId, assetName, contents, assetFile);
+ }
+
+ @Override
+ public Asset saveAsset(final String connectorId, final String assetId, final String assetName, final InputStream contents) throws IOException {
+ final File assetFile = getFile(connectorId, assetId, assetName);
+ return saveAsset(connectorId, assetId, assetName, contents, assetFile);
+ }
+
+ @Override
+ public Optional<Asset> getAsset(final String id) {
+ return Optional.ofNullable(assets.get(id));
+ }
+
+ @Override
+ public List<Asset> getAssets(final String connectorId) {
+ final List<Asset> allAssets = new ArrayList<>(assets.values());
+ final List<Asset> ownerAssets = new ArrayList<>();
+ for (final Asset asset : allAssets) {
+ if (asset.getOwnerIdentifier().equals(connectorId)) {
+ ownerAssets.add(asset);
+ }
+ }
+ return ownerAssets;
+ }
+
+ @Override
+ public Asset createMissingAsset(final String connectorId, final String assetName) {
+ final String assetId = generateAssetId();
+ final File file = getFile(connectorId, assetId, assetName);
+ final Asset asset = new StandardAsset(assetId, connectorId, assetName, file, null);
+ assets.put(assetId, asset);
+ return asset;
+ }
+
+ @Override
+ public Optional<Asset> deleteAsset(final String id) {
+ final Asset removed = assets.remove(id);
+ if (removed == null) {
+ return Optional.empty();
+ }
+
+ final File assetFile = removed.getFile();
+ if (assetFile.exists()) {
+ deleteFile(assetFile);
+
+ final File assetIdDir = assetFile.getParentFile();
+ deleteFile(assetIdDir);
+
+ final File connectorDir = assetIdDir.getParentFile();
+ final File[] children = connectorDir.listFiles();
+ if (children != null && children.length == 0) {
+ deleteFile(connectorDir);
+ }
+ }
+
+ return Optional.of(removed);
+ }
+
+ private Asset saveAsset(final String connectorId, final String assetId, final String assetName, final InputStream contents, final File assetFile) throws IOException {
+ final File dir = assetFile.getParentFile();
+ if (!dir.exists()) {
+ try {
+ Files.createDirectories(dir.toPath());
+ } catch (final IOException ioe) {
+ throw new IOException("Could not create directory in order to store connector asset", ioe);
+ }
+ }
+
+ try {
+ Files.copy(contents, assetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (final Exception e) {
+ throw new IOException("Failed to write connector asset to file " + assetFile.getAbsolutePath(), e);
+ }
+
+ final String digest = computeDigest(assetFile);
+ final Asset asset = new StandardAsset(assetId, connectorId, assetName, assetFile, digest);
+ assets.put(assetId, asset);
+ return asset;
+ }
+
+ private void deleteFile(final File file) {
+ try {
+ Files.delete(file.toPath());
+ } catch (final IOException e) {
+ logger.warn("Failed to delete [{}]", file.getAbsolutePath(), e);
+ }
+ }
+
+ private String generateAssetId() {
+ return UUID.randomUUID().toString();
+ }
+
+ private File getFile(final String connectorId, final String assetId, final String assetName) {
+ final Path parentPath = assetStorageLocation.toPath().normalize();
+ final Path assetPath = Paths.get(connectorId, assetId, assetName).normalize();
+ final Path fullPath = parentPath.resolve(assetPath);
+ return fullPath.toFile();
+ }
+
+ private String getStorageLocation(final AssetManagerInitializationContext initializationContext) {
+ final String storageLocation = initializationContext.getProperties().get(ASSET_STORAGE_LOCATION_PROPERTY);
+ return storageLocation == null ? DEFAULT_ASSET_STORAGE_LOCATION : storageLocation;
+ }
+
+ private void recoverLocalAssets() throws IOException {
+ final File[] connectorDirectories = assetStorageLocation.listFiles();
+ if (connectorDirectories == null) {
+ throw new IOException("Unable to list files for connector asset storage location %s".formatted(assetStorageLocation.getAbsolutePath()));
+ }
+
+ for (final File connectorDirectory : connectorDirectories) {
+ if (!connectorDirectory.isDirectory()) {
+ continue;
+ }
+
+ final String connectorId = connectorDirectory.getName();
+ final File[] assetIdDirectories = connectorDirectory.listFiles();
+ if (assetIdDirectories == null) {
+ logger.warn("Unable to list directory [{}]", connectorDirectory.getAbsolutePath());
+ continue;
+ }
+
+ for (final File assetIdDirectory : assetIdDirectories) {
+ final String assetId = assetIdDirectory.getName();
+
+ final File[] assetFiles = assetIdDirectory.listFiles();
+ if (assetFiles == null) {
+ logger.warn("Unable to list directory [{}]", assetIdDirectory.getAbsolutePath());
+ continue;
+ }
+
+ for (final File assetFile : assetFiles) {
+ final String assetName = assetFile.getName();
+ final String digest = computeDigest(assetFile);
+ final Asset asset = new StandardAsset(assetId, connectorId, assetName, assetFile, digest);
+ assets.put(assetId, asset);
+ }
+ }
+ }
+ }
+
+ private String computeDigest(final File file) throws IOException {
+ return HexFormat.of().formatHex(FileDigestUtils.getDigest(file));
+ }
+
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
index 1abafb7c93..50a896ebf5 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
@@ -1085,7 +1085,7 @@ public class VersionedComponentFlowMapper {
switch (valueReference) {
case StringLiteralValue stringLiteral -> versionedReference.setValue(stringLiteral.getValue());
- case AssetReference assetRef -> versionedReference.setAssetId(assetRef.getAssetIdentifier());
+ case AssetReference assetRef -> versionedReference.setAssetIds(assetRef.getAssetIdentifiers());
case SecretReference secretRef -> {
versionedReference.setProviderId(secretRef.getProviderId());
versionedReference.setProviderName(secretRef.getProviderName());
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/resources/META-INF/services/org.apache.nifi.asset.AssetManager b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/resources/META-INF/services/org.apache.nifi.asset.AssetManager
index 6fbf05d949..3a438fb65d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/resources/META-INF/services/org.apache.nifi.asset.AssetManager
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/resources/META-INF/services/org.apache.nifi.asset.AssetManager
@@ -13,4 +13,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-org.apache.nifi.asset.StandardAssetManager
\ No newline at end of file
+org.apache.nifi.asset.StandardAssetManager
+org.apache.nifi.asset.StandardConnectorAssetManager
\ No newline at end of file
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/asset/StandardConnectorAssetManagerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/asset/StandardConnectorAssetManagerTest.java
new file mode 100644
index 0000000000..fa13d28bba
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/asset/StandardConnectorAssetManagerTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.asset;
+
+import org.apache.nifi.controller.NodeTypeProvider;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+class StandardConnectorAssetManagerTest {
+
+ @Test
+ void testInitializeRecoversExistingAssets(@TempDir final Path tempDir) throws Exception {
+ final Path storageDirectory = tempDir.resolve("assets");
+ final String connectorIdentifier = "connector-1";
+ final String assetIdentifier = "asset-1";
+ final String assetName = "asset.txt";
+
+ final Path assetDirectory = storageDirectory.resolve(connectorIdentifier).resolve(assetIdentifier);
+ Files.createDirectories(assetDirectory);
+ final Path assetPath = assetDirectory.resolve(assetName);
+ Files.writeString(assetPath, "existing-content", StandardCharsets.UTF_8);
+
+ final StandardConnectorAssetManager manager = new StandardConnectorAssetManager();
+ final AssetManagerInitializationContext context = createInitializationContext(storageDirectory.toFile());
+ manager.initialize(context);
+
+ final List<Asset> assets = manager.getAssets(connectorIdentifier);
+ assertEquals(1, assets.size());
+
+ final Asset recoveredAsset = assets.getFirst();
+ assertEquals(assetName, recoveredAsset.getName());
+ assertEquals(connectorIdentifier, recoveredAsset.getOwnerIdentifier());
+ assertTrue(recoveredAsset.getDigest().isPresent());
+ assertEquals(assetPath.toFile().getAbsolutePath(), recoveredAsset.getFile().getAbsolutePath());
+ }
+
+ @Test
+ void testCreateAssetAndGetAsset(@TempDir final Path tempDir) throws Exception {
+ final Path storageDirectory = tempDir.resolve("assets");
+ final StandardConnectorAssetManager manager = new StandardConnectorAssetManager();
+ final AssetManagerInitializationContext context = createInitializationContext(storageDirectory.toFile());
+ manager.initialize(context);
+
+ assertTrue(Files.isDirectory(storageDirectory));
+
+ final String connectorIdentifier = "connector-1";
+ final String assetName = "created-asset.txt";
+ final byte[] contents = "created-content".getBytes(StandardCharsets.UTF_8);
+ final InputStream inputStream = new ByteArrayInputStream(contents);
+
+ final Asset createdAsset = manager.createAsset(connectorIdentifier, assetName, inputStream);
+ assertNotNull(createdAsset);
+ assertEquals(connectorIdentifier, createdAsset.getOwnerIdentifier());
+ assertEquals(assetName, createdAsset.getName());
+ assertTrue(createdAsset.getDigest().isPresent());
+ assertTrue(createdAsset.getFile().exists());
+
+ final Optional<Asset> retrievedByIdentifier = manager.getAsset(createdAsset.getIdentifier());
+ assertTrue(retrievedByIdentifier.isPresent());
+ assertEquals(createdAsset.getIdentifier(), retrievedByIdentifier.get().getIdentifier());
+
+ final List<Asset> connectorAssets = manager.getAssets(connectorIdentifier);
+ assertEquals(1, connectorAssets.size());
+ assertEquals(createdAsset.getIdentifier(), connectorAssets.get(0).getIdentifier());
+ }
+
+ @Test
+ void testCreateMissingAssetDoesNotCreateFile(@TempDir final Path tempDir) {
+ final Path storageDirectory = tempDir.resolve("assets");
+ final StandardConnectorAssetManager manager = new StandardConnectorAssetManager();
+ final AssetManagerInitializationContext context = createInitializationContext(storageDirectory.toFile());
+ manager.initialize(context);
+
+ final String connectorIdentifier = "connector-2";
+ final String assetName = "missing-asset.txt";
+
+ final Asset missingAsset = manager.createMissingAsset(connectorIdentifier, assetName);
+ assertNotNull(missingAsset);
+ assertEquals(connectorIdentifier, missingAsset.getOwnerIdentifier());
+ assertEquals(assetName, missingAsset.getName());
+ assertFalse(missingAsset.getDigest().isPresent());
+ assertFalse(missingAsset.getFile().exists());
+
+ final Optional<Asset> retrieved = manager.getAsset(missingAsset.getIdentifier());
+ assertTrue(retrieved.isPresent());
+ assertEquals(missingAsset.getIdentifier(), retrieved.get().getIdentifier());
+
+ final List<Asset> connectorAssets = manager.getAssets(connectorIdentifier);
+ assertEquals(1, connectorAssets.size());
+ assertEquals(missingAsset.getIdentifier(), connectorAssets.getFirst().getIdentifier());
+ }
+
+ @Test
+ void testDeleteAssetRemovesFilesAndDirectories(@TempDir final Path tempDir) throws Exception {
+ final Path storageDirectory = tempDir.resolve("assets");
+ final StandardConnectorAssetManager manager = new StandardConnectorAssetManager();
+ final AssetManagerInitializationContext context = createInitializationContext(storageDirectory.toFile());
+ manager.initialize(context);
+
+ final String connectorIdentifier = "connector-3";
+ final String assetName = "deletable-asset.txt";
+ final byte[] contents = "deletable-content".getBytes(StandardCharsets.UTF_8);
+ final InputStream inputStream = new ByteArrayInputStream(contents);
+
+ final Asset asset = manager.createAsset(connectorIdentifier, assetName, inputStream);
+ final File assetFile = asset.getFile();
+ final File assetDirectory = assetFile.getParentFile();
+ final File connectorDirectory = assetDirectory.getParentFile();
+
+ assertTrue(assetFile.exists());
+ assertTrue(assetDirectory.exists());
+ assertTrue(connectorDirectory.exists());
+
+ final Optional<Asset> deleted = manager.deleteAsset(asset.getIdentifier());
+ assertTrue(deleted.isPresent());
+ assertFalse(assetFile.exists());
+ assertFalse(assetDirectory.exists());
+ assertFalse(connectorDirectory.exists());
+
+ assertTrue(manager.getAsset(asset.getIdentifier()).isEmpty());
+ assertTrue(manager.getAssets(connectorIdentifier).isEmpty());
+ }
+
+ @Test
+ void testDeleteNonExistentAssetReturnsEmpty(@TempDir final Path tempDir) {
+ final Path storageDirectory = tempDir.resolve("assets");
+ final StandardConnectorAssetManager manager = new StandardConnectorAssetManager();
+ final AssetManagerInitializationContext context = createInitializationContext(storageDirectory.toFile());
+ manager.initialize(context);
+
+ final Optional<Asset> deleted = manager.deleteAsset("non-existent-id");
+ assertTrue(deleted.isEmpty());
+ }
+
+ private AssetManagerInitializationContext createInitializationContext(final File storageDirectory) {
+ final Map<String, String> properties = new HashMap<>();
+ properties.put(StandardConnectorAssetManager.ASSET_STORAGE_LOCATION_PROPERTY, storageDirectory.getAbsolutePath());
+
+ final AssetReferenceLookup assetReferenceLookup = mock(AssetReferenceLookup.class);
+ final NodeTypeProvider nodeTypeProvider = mock(NodeTypeProvider.class);
+
+ return new StandardAssetManagerInitializationContext(assetReferenceLookup, properties, nodeTypeProvider);
+ }
+}
+
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorAssetRepository.java
similarity index 63%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
copy to nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorAssetRepository.java
index 0f0c50c394..8305b0b0e4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorAssetRepository.java
@@ -14,24 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.nifi.components.connector;
-import org.apache.nifi.components.connector.secrets.SecretsManager;
-import org.apache.nifi.controller.NodeTypeProvider;
-import org.apache.nifi.controller.flow.FlowManager;
-import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.asset.Asset;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Optional;
-public interface ConnectorRepositoryInitializationContext {
+public interface ConnectorAssetRepository {
- FlowManager getFlowManager();
+ Asset storeAsset(String connectorId, String assetId, String assetName, InputStream content) throws IOException;
- ExtensionManager getExtensionManager();
+ Optional<Asset> getAsset(String assetId);
- SecretsManager getSecretsManager();
+ List<Asset> getAssets(String connectorId);
- NodeTypeProvider getNodeTypeProvider();
+ void deleteAsset(String assetId);
- ConnectorRequestReplicator getRequestReplicator();
+ void deleteAssets(String connectorId);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
index be39e14dc6..2a134d8e5e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
@@ -96,4 +96,5 @@ public interface ConnectorRepository {
FrameworkConnectorInitializationContextBuilder createInitializationContextBuilder();
+ ConnectorAssetRepository getAssetRepository();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
index 0f0c50c394..9723e85e16 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
@@ -18,6 +18,7 @@
package org.apache.nifi.components.connector;
import org.apache.nifi.components.connector.secrets.SecretsManager;
+import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.controller.NodeTypeProvider;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.nar.ExtensionManager;
@@ -30,6 +31,8 @@ public interface ConnectorRepositoryInitializationContext {
SecretsManager getSecretsManager();
+ AssetManager getAssetManager();
+
NodeTypeProvider getNodeTypeProvider();
ConnectorRequestReplicator getRequestReplicator();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/AssetsRestApiClient.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/AssetsRestApiClient.java
index b8f2052dd5..29cecc02a0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/AssetsRestApiClient.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/AssetsRestApiClient.java
@@ -40,13 +40,14 @@ class AssetsRestApiClient extends NiFiRestApiClient {
private static final String NIFI_API_PATH_PART = "nifi-api";
private static final String PARAMETER_CONTEXTS_PATH_PART = "parameter-contexts";
+ private static final String CONNECTORS_PATH_PART = "connectors";
private static final String ASSETS_PATH_PART = "assets";
public AssetsRestApiClient(final WebClientService webClientService, final String host, final int port, final boolean secure) {
super(webClientService, host, port, secure);
}
- public AssetsEntity getAssets(final String parameterContextId) {
+ public AssetsEntity getParameterContextAssets(final String parameterContextId) {
final URI requestUri = new StandardHttpUriBuilder()
.scheme(baseUri.getScheme())
.host(baseUri.getHost())
@@ -56,7 +57,7 @@ class AssetsRestApiClient extends NiFiRestApiClient {
.addPathSegment(parameterContextId)
.addPathSegment(ASSETS_PATH_PART)
.build();
- logger.debug("Requesting Asset listing from {}", requestUri);
+ logger.debug("Requesting Parameter Context Asset listing from {}", requestUri);
// Send the replicated header so that the cluster coordinator does not replicate the request, otherwise this call can happen when no nodes
// are considered connected and result in a 500 exception that can't easily be differentiated from other unknown errors
@@ -68,7 +69,7 @@ class AssetsRestApiClient extends NiFiRestApiClient {
return executeEntityRequest(requestUri, requestBodySpec, AssetsEntity.class);
}
- public InputStream getAssetContent(final String parameterContextId, final String assetId) {
+ public InputStream getParameterContextAssetContent(final String parameterContextId, final String assetId) {
final URI requestUri = new StandardHttpUriBuilder()
.scheme(baseUri.getScheme())
.host(baseUri.getHost())
@@ -79,7 +80,53 @@ class AssetsRestApiClient extends NiFiRestApiClient {
.addPathSegment(ASSETS_PATH_PART)
.addPathSegment(assetId)
.build();
- logger.debug("Getting asset content from {}", requestUri);
+ logger.debug("Getting Parameter Context asset content from {}", requestUri);
+
+ try {
+ final HttpResponseEntity response = webClientService.get()
+ .uri(requestUri)
+ .header(ACCEPT_HEADER, APPLICATION_OCTET_STREAM)
+ .retrieve();
+ return getResponseBody(requestUri, response);
+ } catch (final WebClientServiceException e) {
+ throw new NiFiRestApiRetryableException(e.getMessage(), e);
+ }
+ }
+
+ public AssetsEntity getConnectorAssets(final String connectorId) {
+ final URI requestUri = new StandardHttpUriBuilder()
+ .scheme(baseUri.getScheme())
+ .host(baseUri.getHost())
+ .port(baseUri.getPort())
+ .addPathSegment(NIFI_API_PATH_PART)
+ .addPathSegment(CONNECTORS_PATH_PART)
+ .addPathSegment(connectorId)
+ .addPathSegment(ASSETS_PATH_PART)
+ .build();
+ logger.debug("Requesting Connector Asset listing from {}", requestUri);
+
+ // Send the replicated header so that the cluster coordinator does not replicate the request, otherwise this call can happen when no nodes
+ // are considered connected and result in a 500 exception that can't easily be differentiated from other unknown errors
+ final HttpRequestBodySpec requestBodySpec = webClientService.get()
+ .uri(requestUri)
+ .header(ACCEPT_HEADER, APPLICATION_JSON)
+ .header(REQUEST_REPLICATED_HEADER, Boolean.TRUE.toString());
+
+ return executeEntityRequest(requestUri, requestBodySpec, AssetsEntity.class);
+ }
+
+ public InputStream getConnectorAssetContent(final String connectorId, final String assetId) {
+ final URI requestUri = new StandardHttpUriBuilder()
+ .scheme(baseUri.getScheme())
+ .host(baseUri.getHost())
+ .port(baseUri.getPort())
+ .addPathSegment(NIFI_API_PATH_PART)
+ .addPathSegment(CONNECTORS_PATH_PART)
+ .addPathSegment(connectorId)
+ .addPathSegment(ASSETS_PATH_PART)
+ .addPathSegment(assetId)
+ .build();
+ logger.debug("Getting Connector asset content from {}", requestUri);
try {
final HttpResponseEntity response = webClientService.get()
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetComponentManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetComponentManager.java
index 9954c947c1..9449ce8ec6 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetComponentManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetComponentManager.java
@@ -58,7 +58,7 @@ public class StandardAssetComponentManager implements AssetComponentManager {
@Override
public void restartReferencingComponentsAsync(final Asset asset) {
final String assetName = asset.getName();
- final String paramContextId = asset.getParameterContextIdentifier();
+ final String paramContextId = asset.getOwnerIdentifier();
Thread.ofVirtual().name("Restart Components for Asset [%s] in ParameterContext [%s]".formatted(assetName, paramContextId)).start(() -> {
try {
restartReferencingComponents(asset);
@@ -71,7 +71,7 @@ public class StandardAssetComponentManager implements AssetComponentManager {
@Override
public void restartReferencingComponents(final Asset asset) {
final ParameterContextManager parameterContextManager = flowManager.getParameterContextManager();
- final ParameterContext parameterContext = parameterContextManager.getParameterContext(asset.getParameterContextIdentifier());
+ final ParameterContext parameterContext = parameterContextManager.getParameterContext(asset.getOwnerIdentifier());
// Determine which parameters reference the given asset
final Set<Parameter> parametersReferencingAsset = getParametersReferencingAsset(parameterContext, asset);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetSynchronizer.java
index d9f1cd44d3..604cdef10f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetSynchronizer.java
@@ -212,7 +212,7 @@ public class StandardAssetSynchronizer implements AssetSynchronizer {
private AssetsEntity listAssets(final AssetsRestApiClient assetsRestApiClient, final String parameterContextId) {
try {
- return assetsRestApiClient.getAssets(parameterContextId);
+ return assetsRestApiClient.getParameterContextAssets(parameterContextId);
} catch (final NiFiRestApiRetryableException e) {
final Throwable rootCause = ExceptionUtils.getRootCause(e);
logger.warn("{}, root cause [{}]: retrying", e.getMessage(), rootCause.getMessage());
@@ -242,7 +242,7 @@ public class StandardAssetSynchronizer implements AssetSynchronizer {
if (Boolean.TRUE == coordinatorAsset.getMissingContent()) {
return assetManager.createMissingAsset(parameterContextId, assetName);
} else {
- try (final InputStream assetInputStream = assetsRestApiClient.getAssetContent(parameterContextId, assetId)) {
+ try (final InputStream assetInputStream = assetsRestApiClient.getParameterContextAssetContent(parameterContextId, assetId)) {
return assetManager.createAsset(parameterContextId, assetName, assetInputStream);
} catch (final NiFiRestApiRetryableException e) {
final Throwable rootCause = ExceptionUtils.getRootCause(e);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardConnectorAssetSynchronizer.java
similarity index 53%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetSynchronizer.java
copy to nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardConnectorAssetSynchronizer.java
index d9f1cd44d3..2eb1aa5a9a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardAssetSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/asset/StandardConnectorAssetSynchronizer.java
@@ -21,11 +21,9 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.nifi.client.NiFiRestApiRetryableException;
import org.apache.nifi.cluster.coordination.ClusterCoordinator;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.flow.FlowManager;
-import org.apache.nifi.parameter.Parameter;
-import org.apache.nifi.parameter.ParameterContext;
-import org.apache.nifi.parameter.ParameterContextManager;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.api.dto.AssetDTO;
import org.apache.nifi.web.api.entity.AssetEntity;
@@ -38,39 +36,34 @@ import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.Instant;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
- * Synchronizes assets from the cluster coordinator using the NiFi REST API.
+ * Synchronizes connector assets from the cluster coordinator using the NiFi REST API.
*/
-public class StandardAssetSynchronizer implements AssetSynchronizer {
+public class StandardConnectorAssetSynchronizer implements AssetSynchronizer {
- private static final Logger logger = LoggerFactory.getLogger(StandardAssetSynchronizer.class);
+ private static final Logger logger = LoggerFactory.getLogger(StandardConnectorAssetSynchronizer.class);
private static final Duration CLUSTER_COORDINATOR_RETRY_DURATION = Duration.ofSeconds(60);
private static final Duration LIST_ASSETS_RETRY_DURATION = Duration.ofMinutes(5);
private static final Duration SYNC_ASSET_RETRY_DURATION = Duration.ofSeconds(30);
private final AssetManager assetManager;
- private final AssetComponentManager assetComponentManager;
private final FlowManager flowManager;
private final ClusterCoordinator clusterCoordinator;
private final WebClientService webClientService;
private final NiFiProperties properties;
- public StandardAssetSynchronizer(final FlowController flowController,
- final ClusterCoordinator clusterCoordinator,
- final WebClientService webClientService,
- final NiFiProperties properties,
- final AssetComponentManager assetComponentManager) {
- this.assetManager = flowController.getAssetManager();
- this.assetComponentManager = assetComponentManager;
+ public StandardConnectorAssetSynchronizer(final FlowController flowController,
+ final ClusterCoordinator clusterCoordinator,
+ final WebClientService webClientService,
+ final NiFiProperties properties) {
+ this.assetManager = flowController.getConnectorAssetManager();
this.flowManager = flowController.getFlowManager();
this.clusterCoordinator = clusterCoordinator;
this.webClientService = webClientService;
@@ -80,139 +73,104 @@ public class StandardAssetSynchronizer implements AssetSynchronizer {
@Override
public void synchronize() {
if (clusterCoordinator == null) {
- logger.info("Clustering is not configured: Asset synchronization disabled");
+ logger.info("Clustering is not configured: Connector asset synchronization disabled");
return;
}
- // This sync method is called from the method that loads the flow from a connection response, which means there must already be a cluster coordinator to
- // have gotten a response from, but during testing there were cases where calling clusterCoordinator.getElectedActiveCoordinatorNode() was still null, so
- // the helper method here will keep checking for the identifier up to a certain threshold to avoid slight timing issues
final NodeIdentifier coordinatorNodeId = getElectedActiveCoordinatorNode();
if (coordinatorNodeId == null) {
- logger.warn("Unable to obtain the node identifier for the cluster coordinator: Asset synchronization disabled");
+ logger.warn("Unable to obtain the node identifier for the cluster coordinator: Connector asset synchronization disabled");
return;
}
if (clusterCoordinator.isActiveClusterCoordinator()) {
- logger.info("Current node is the cluster coordinator: Asset synchronization disabled");
+ logger.info("Current node is the cluster coordinator: Connector asset synchronization disabled");
return;
}
final String coordinatorAddress = coordinatorNodeId.getApiAddress();
final int coordinatorPort = coordinatorNodeId.getApiPort();
final AssetsRestApiClient assetsRestApiClient = new AssetsRestApiClient(webClientService, coordinatorAddress, coordinatorPort, properties.isHTTPSConfigured());
- logger.info("Synchronizing assets with cluster coordinator at {}:{}", coordinatorAddress, coordinatorPort);
+ logger.info("Synchronizing connector assets with cluster coordinator at {}:{}", coordinatorAddress, coordinatorPort);
- final ParameterContextManager parameterContextManager = flowManager.getParameterContextManager();
- final Set<ParameterContext> parameterContexts = parameterContextManager.getParameterContexts();
- logger.info("Found {} parameter contexts for synchronizing assets", parameterContexts.size());
+ final List<ConnectorNode> connectors = flowManager.getAllConnectors();
+ logger.info("Found {} connectors for synchronizing assets", connectors.size());
- final List<Asset> allSyncedAssets = new ArrayList<>();
- for (final ParameterContext parameterContext : parameterContexts) {
+ for (final ConnectorNode connector : connectors) {
try {
- final List<Asset> syncedAssets = synchronize(assetsRestApiClient, parameterContext);
- allSyncedAssets.addAll(syncedAssets);
+ synchronize(assetsRestApiClient, connector);
} catch (final Exception e) {
- logger.error("Failed to synchronize assets for parameter context [{}]", parameterContext.getIdentifier(), e);
- }
- }
-
- // Restart components that reference the synchronized assets
- if (!allSyncedAssets.isEmpty()) {
- try {
- logger.info("Restarting components referencing {} synchronized assets", allSyncedAssets.size());
- assetComponentManager.restartReferencingComponents(allSyncedAssets);
- } catch (final Exception e) {
- logger.error("Failed to restart components referencing synchronized assets", e);
+ logger.error("Failed to synchronize assets for connector [{}]", connector.getIdentifier(), e);
}
}
}
- private List<Asset> synchronize(final AssetsRestApiClient assetsRestApiClient, final ParameterContext parameterContext) {
- final List<Asset> syncedAssets = new ArrayList<>();
-
- final Map<String, Asset> existingAssets = parameterContext.getParameters().values().stream()
- .map(Parameter::getReferencedAssets)
- .flatMap(Collection::stream)
+ private void synchronize(final AssetsRestApiClient assetsRestApiClient, final ConnectorNode connector) {
+ final String connectorId = connector.getIdentifier();
+ final Map<String, Asset> existingAssets = assetManager.getAssets(connectorId).stream()
.collect(Collectors.toMap(Asset::getIdentifier, Function.identity()));
- if (existingAssets.isEmpty()) {
- logger.info("Parameter context [{}] does not contain any assets to synchronize", parameterContext.getIdentifier());
- return syncedAssets;
- }
-
- logger.info("Parameter context [{}] has {} assets on the current node", parameterContext.getIdentifier(), existingAssets.size());
-
- final AssetsEntity coordinatorAssetsEntity = listAssetsWithRetry(assetsRestApiClient, parameterContext.getIdentifier());
+ final AssetsEntity coordinatorAssetsEntity = listConnectorAssetsWithRetry(assetsRestApiClient, connectorId);
if (coordinatorAssetsEntity == null) {
- logger.error("Timeout listing assets from cluster coordinator for parameter context [{}]", parameterContext.getIdentifier());
- return syncedAssets;
+ logger.error("Timeout listing assets from cluster coordinator for connector [{}]", connectorId);
+ return;
}
final Collection<AssetEntity> coordinatorAssets = coordinatorAssetsEntity.getAssets();
if (coordinatorAssets == null || coordinatorAssets.isEmpty()) {
- logger.info("Parameter context [{}] did not return any assets from the cluster coordinator", parameterContext.getIdentifier());
- return syncedAssets;
+ logger.info("Connector [{}] did not return any assets from the cluster coordinator", connectorId);
+ return;
}
- logger.info("Parameter context [{}] returned {} assets from the cluster coordinator", parameterContext.getIdentifier(), coordinatorAssets.size());
+ logger.info("Connector [{}] returned {} assets from the cluster coordinator", connectorId, coordinatorAssets.size());
for (final AssetEntity coordinatorAssetEntity : coordinatorAssets) {
final AssetDTO coordinatorAsset = coordinatorAssetEntity.getAsset();
final Asset matchingAsset = existingAssets.get(coordinatorAsset.getId());
try {
- final Asset syncedAsset = synchronize(assetsRestApiClient, parameterContext, coordinatorAsset, matchingAsset);
- if (syncedAsset != null) {
- logger.info("Successfully synchronized asset [id={},name={}] for parameter context [{}]",
- syncedAsset.getIdentifier(), syncedAsset.getName(), parameterContext.getIdentifier());
- syncedAssets.add(syncedAsset);
- }
+ synchronize(assetsRestApiClient, connectorId, coordinatorAsset, matchingAsset);
} catch (final Exception e) {
- logger.error("Failed to synchronize asset [id={},name={}] for parameter context [{}]",
- coordinatorAsset.getId(), coordinatorAsset.getName(), parameterContext.getIdentifier(), e);
+ logger.error("Failed to synchronize asset [id={},name={}] for connector [{}]",
+ coordinatorAsset.getId(), coordinatorAsset.getName(), connectorId, e);
}
}
-
- return syncedAssets;
}
- private Asset synchronize(final AssetsRestApiClient assetsRestApiClient, final ParameterContext parameterContext, final AssetDTO coordinatorAsset, final Asset matchingAsset) {
- final String paramContextId = parameterContext.getIdentifier();
+ private void synchronize(final AssetsRestApiClient assetsRestApiClient, final String connectorId, final AssetDTO coordinatorAsset, final Asset matchingAsset) {
final String assetId = coordinatorAsset.getId();
final String assetName = coordinatorAsset.getName();
if (matchingAsset == null || !matchingAsset.getFile().exists()) {
- logger.info("Synchronizing missing asset [id={},name={}] for parameter context [{}]", assetId, assetName, paramContextId);
- return synchronizeAssetWithRetry(assetsRestApiClient, paramContextId, coordinatorAsset);
+ logger.info("Synchronizing missing asset [id={},name={}] for connector [{}]", assetId, assetName, connectorId);
+ synchronizeConnectorAssetWithRetry(assetsRestApiClient, connectorId, coordinatorAsset);
} else {
final String coordinatorAssetDigest = coordinatorAsset.getDigest();
final String matchingAssetDigest = matchingAsset.getDigest().orElse(null);
if (!coordinatorAssetDigest.equals(matchingAssetDigest)) {
- logger.info("Synchronizing asset [id={},name={}] with updated digest [{}] for parameter context [{}]",
- assetId, assetName, coordinatorAssetDigest, paramContextId);
- return synchronizeAssetWithRetry(assetsRestApiClient, paramContextId, coordinatorAsset);
+ logger.info("Synchronizing asset [id={},name={}] with updated digest [{}] for connector [{}]",
+ assetId, assetName, coordinatorAssetDigest, connectorId);
+ synchronizeConnectorAssetWithRetry(assetsRestApiClient, connectorId, coordinatorAsset);
} else {
- logger.info("Coordinator asset [id={},name={}] found for parameter context [{}]: retrieval not required", assetId, assetName, paramContextId);
- return null;
+ logger.info("Coordinator asset [id={},name={}] found for connector [{}]: retrieval not required", assetId, assetName, connectorId);
}
}
}
- private AssetsEntity listAssetsWithRetry(final AssetsRestApiClient assetsRestApiClient, final String parameterContextId) {
+ private AssetsEntity listConnectorAssetsWithRetry(final AssetsRestApiClient assetsRestApiClient, final String connectorId) {
final Instant expirationTime = Instant.ofEpochMilli(System.currentTimeMillis() + LIST_ASSETS_RETRY_DURATION.toMillis());
while (System.currentTimeMillis() < expirationTime.toEpochMilli()) {
- final AssetsEntity assetsEntity = listAssets(assetsRestApiClient, parameterContextId);
+ final AssetsEntity assetsEntity = listConnectorAssets(assetsRestApiClient, connectorId);
if (assetsEntity != null) {
return assetsEntity;
}
- logger.info("Unable to list assets from cluster coordinator for parameter context [{}]: retrying until [{}]", parameterContextId, expirationTime);
+ logger.info("Unable to list assets from cluster coordinator for connector [{}]: retrying until [{}]", connectorId, expirationTime);
sleep(Duration.ofSeconds(5));
}
return null;
}
- private AssetsEntity listAssets(final AssetsRestApiClient assetsRestApiClient, final String parameterContextId) {
+ private AssetsEntity listConnectorAssets(final AssetsRestApiClient assetsRestApiClient, final String connectorId) {
try {
- return assetsRestApiClient.getAssets(parameterContextId);
+ return assetsRestApiClient.getConnectorAssets(connectorId);
} catch (final NiFiRestApiRetryableException e) {
final Throwable rootCause = ExceptionUtils.getRootCause(e);
logger.warn("{}, root cause [{}]: retrying", e.getMessage(), rootCause.getMessage());
@@ -220,30 +178,27 @@ public class StandardAssetSynchronizer implements AssetSynchronizer {
}
}
- private Asset synchronizeAssetWithRetry(final AssetsRestApiClient assetsRestApiClient, final String parameterContextId, final AssetDTO coordinatorAsset) {
+ private void synchronizeConnectorAssetWithRetry(final AssetsRestApiClient assetsRestApiClient, final String connectorId, final AssetDTO coordinatorAsset) {
final Instant expirationTime = Instant.ofEpochMilli(System.currentTimeMillis() + SYNC_ASSET_RETRY_DURATION.toMillis());
while (System.currentTimeMillis() < expirationTime.toEpochMilli()) {
- final Asset syncedAsset = synchronizeAsset(assetsRestApiClient, parameterContextId, coordinatorAsset);
+ final Asset syncedAsset = synchronizeConnectorAsset(assetsRestApiClient, connectorId, coordinatorAsset);
if (syncedAsset != null) {
- logger.info("Synchronizing asset complete [id={},name={},file={},digest={}]",
- syncedAsset.getIdentifier(), syncedAsset.getName(), syncedAsset.getFile().getAbsolutePath(), syncedAsset.getDigest().orElse("[Missing]"));
- return syncedAsset;
+ return;
}
- logger.info("Unable to synchronize asset [id={},name={}] for parameter context [{}]: retrying until [{}]",
- coordinatorAsset.getId(), coordinatorAsset.getName(), parameterContextId, expirationTime);
+ logger.info("Unable to synchronize asset [id={},name={}] for connector [{}]: retrying until [{}]",
+ coordinatorAsset.getId(), coordinatorAsset.getName(), connectorId, expirationTime);
sleep(Duration.ofSeconds(5));
}
- return null;
}
- private Asset synchronizeAsset(final AssetsRestApiClient assetsRestApiClient, final String parameterContextId, final AssetDTO coordinatorAsset) {
+ private Asset synchronizeConnectorAsset(final AssetsRestApiClient assetsRestApiClient, final String connectorId, final AssetDTO coordinatorAsset) {
final String assetId = coordinatorAsset.getId();
final String assetName = coordinatorAsset.getName();
if (Boolean.TRUE == coordinatorAsset.getMissingContent()) {
- return assetManager.createMissingAsset(parameterContextId, assetName);
+ return assetManager.createMissingAsset(connectorId, assetName);
} else {
- try (final InputStream assetInputStream = assetsRestApiClient.getAssetContent(parameterContextId, assetId)) {
- return assetManager.createAsset(parameterContextId, assetName, assetInputStream);
+ try (final InputStream assetInputStream = assetsRestApiClient.getConnectorAssetContent(connectorId, assetId)) {
+ return assetManager.saveAsset(connectorId, assetId, assetName, assetInputStream);
} catch (final NiFiRestApiRetryableException e) {
final Throwable rootCause = ExceptionUtils.getRootCause(e);
logger.warn("{}, root cause [{}]: retrying", e.getMessage(), rootCause.getMessage());
@@ -278,3 +233,5 @@ public class StandardAssetSynchronizer implements AssetSynchronizer {
}
}
}
+
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorAssetRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorAssetRepository.java
new file mode 100644
index 0000000000..2ada8b424d
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorAssetRepository.java
@@ -0,0 +1,63 @@
+/*
+ * 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.components.connector;
+
+import org.apache.nifi.asset.Asset;
+import org.apache.nifi.asset.AssetManager;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Optional;
+
+public class StandardConnectorAssetRepository implements ConnectorAssetRepository {
+
+ private final AssetManager assetManager;
+
+ public StandardConnectorAssetRepository(final AssetManager assetManager) {
+ this.assetManager = assetManager;
+ }
+
+ @Override
+ public Asset storeAsset(final String connectorId, final String assetId, final String assetName, final InputStream content) throws IOException {
+ return assetManager.saveAsset(connectorId, assetId, assetName, content);
+ }
+
+ @Override
+ public Optional<Asset> getAsset(String assetId) {
+ return assetManager.getAsset(assetId);
+ }
+
+ @Override
+ public List<Asset> getAssets(final String connectorId) {
+ return assetManager.getAssets(connectorId);
+ }
+
+ @Override
+ public void deleteAsset(final String assetId) {
+ assetManager.deleteAsset(assetId);
+ }
+
+ @Override
+ public void deleteAssets(final String connectorId) {
+ final List<Asset> assets = assetManager.getAssets(connectorId);
+ for (final Asset asset : assets) {
+ assetManager.deleteAsset(asset.getIdentifier());
+ }
+ }
+
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
index e1722ff9a3..b601d154ed 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
@@ -17,10 +17,12 @@
package org.apache.nifi.components.connector;
+import org.apache.nifi.asset.Asset;
import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.components.connector.secrets.SecretProvider;
import org.apache.nifi.components.connector.secrets.SecretsManager;
+import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
@@ -185,10 +187,7 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
try {
return switch (reference) {
case StringLiteralValue stringLiteral -> stringLiteral;
- case AssetReference assetReference -> assetManager.getAsset(assetReference.getAssetIdentifier())
- .map(asset -> asset.getFile().getAbsolutePath())
- .map(StringLiteralValue::new)
- .orElse(null);
+ case AssetReference assetReference -> resolveAssetReferences(assetReference);
case SecretReference secretReference -> new StringLiteralValue(getSecretValue(secretReference));
};
} catch (final IOException ioe) {
@@ -196,6 +195,18 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
}
}
+ private StringLiteralValue resolveAssetReferences(final AssetReference assetReference) {
+ final Set<String> resolvedAssetValues = new HashSet<>();
+ for (final String assetId : assetReference.getAssetIdentifiers()) {
+ assetManager.getAsset(assetId)
+ .map(Asset::getFile)
+ .map(File::getAbsolutePath)
+ .ifPresent(resolvedAssetValues::add);
+ }
+
+ return new StringLiteralValue(String.join(",", resolvedAssetValues));
+ }
+
private String getSecretValue(final SecretReference secretReference) throws IOException {
final SecretProvider provider = getSecretProvider(secretReference);
if (provider == null) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 80b5d126c6..a0dd520c70 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -17,6 +17,7 @@
package org.apache.nifi.components.connector;
+import org.apache.nifi.asset.Asset;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
@@ -44,9 +45,11 @@ import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarCloseable;
+import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Duration;
@@ -54,6 +57,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -211,7 +215,7 @@ public class StandardConnectorNode implements ConnectorNode {
final ConnectorValueType valueType = ConnectorValueType.valueOf(versionedReference.getValueType());
return switch (valueType) {
case STRING_LITERAL -> new StringLiteralValue(versionedReference.getValue());
- case ASSET_REFERENCE -> new AssetReference(versionedReference.getAssetId());
+ case ASSET_REFERENCE -> new AssetReference(versionedReference.getAssetIds());
case SECRET_REFERENCE -> new SecretReference(versionedReference.getProviderId(), versionedReference.getProviderName(),
versionedReference.getSecretName(), versionedReference.getFullyQualifiedSecretName());
};
@@ -735,16 +739,25 @@ public class StandardConnectorNode implements ConnectorNode {
return switch (valueReference) {
case StringLiteralValue stringLiteralValue -> stringLiteralValue.getValue();
- case AssetReference assetReference -> initializationContext.getAssetManager().getAsset(assetReference.getAssetIdentifier())
- .map(asset -> asset.getFile().getAbsolutePath())
- .orElse(null);
+ case AssetReference assetReference -> resolveAssetReferences(assetReference);
case SecretReference secretReference -> initializationContext.getSecretsManager()
- .getSecret((SecretReference) valueReference)
+ .getSecret(secretReference)
.map(Secret::getValue)
.orElse(null);
};
}
+ private String resolveAssetReferences(final AssetReference assetReference) {
+ final Set<String> resolvedAssetValues = new HashSet<>();
+ for (final String assetId : assetReference.getAssetIdentifiers()) {
+ initializationContext.getAssetManager().getAsset(assetId)
+ .map(Asset::getFile)
+ .map(File::getAbsolutePath)
+ .ifPresent(resolvedAssetValues::add);
+ }
+ return String.join(",", resolvedAssetValues);
+ }
+
private Optional<ConfigurationStep> getConfigurationStep(final String stepName) {
for (final ConfigurationStep step : getConfigurationSteps()) {
if (Objects.equals(step.getName(), stepName)) {
@@ -959,7 +972,7 @@ public class StandardConnectorNode implements ConnectorNode {
results.add(new ValidationResult.Builder()
.subject("Asset Reference")
.valid(false)
- .explanation("The referenced asset [" + invalidAssetRef.getAssetIdentifier() + "] could not be found")
+ .explanation("The referenced assets [" + StringUtils.join(invalidAssetRef.getAssetIdentifiers(), ",") + "] could not be found")
.build());
}
}
@@ -1004,7 +1017,7 @@ public class StandardConnectorNode implements ConnectorNode {
}
}
case AssetReference assetReference -> {
- if (assetReference.getAssetIdentifier() == null) {
+ if (assetReference.getAssetIdentifiers() == null || assetReference.getAssetIdentifiers().isEmpty()) {
return true;
}
}
@@ -1019,7 +1032,11 @@ public class StandardConnectorNode implements ConnectorNode {
return reference.getValueType() == ConnectorValueType.SECRET_REFERENCE;
}
- return reference.getValueType() != ConnectorValueType.SECRET_REFERENCE;
+ if (propertyType == PropertyType.ASSET || propertyType == PropertyType.ASSET_LIST) {
+ return reference.getValueType() == ConnectorValueType.ASSET_REFERENCE;
+ }
+
+ return reference.getValueType() != ConnectorValueType.SECRET_REFERENCE && reference.getValueType() != ConnectorValueType.ASSET_REFERENCE;
}
private ConnectorValidationContext createValidationContext(final FrameworkFlowContext context) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
index 4a1b8ed8b3..1aa7a4b271 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
@@ -18,6 +18,7 @@
package org.apache.nifi.components.connector;
import org.apache.nifi.components.connector.secrets.SecretsManager;
+import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.controller.NodeTypeProvider;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.nar.ExtensionManager;
@@ -26,17 +27,20 @@ public class StandardConnectorRepoInitializationContext implements ConnectorRepo
private final FlowManager flowManager;
private final ExtensionManager extensionManager;
private final SecretsManager secretsManager;
+ private final AssetManager assetManager;
private final NodeTypeProvider nodeTypeProvider;
private final ConnectorRequestReplicator requestReplicator;
public StandardConnectorRepoInitializationContext(final FlowManager flowManager,
final ExtensionManager extensionManager,
final SecretsManager secretsManager,
+ final AssetManager assetManager,
final NodeTypeProvider nodeTypeProvider,
final ConnectorRequestReplicator requestReplicator) {
this.flowManager = flowManager;
this.extensionManager = extensionManager;
this.secretsManager = secretsManager;
+ this.assetManager = assetManager;
this.nodeTypeProvider = nodeTypeProvider;
this.requestReplicator = requestReplicator;
}
@@ -56,6 +60,11 @@ public class StandardConnectorRepoInitializationContext implements ConnectorRepo
return secretsManager;
}
+ @Override
+ public AssetManager getAssetManager() {
+ return assetManager;
+ }
+
@Override
public NodeTypeProvider getNodeTypeProvider() {
return nodeTypeProvider;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index df31268c3c..dfa7a91a94 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -46,12 +46,14 @@ public class StandardConnectorRepository implements ConnectorRepository {
private volatile ExtensionManager extensionManager;
private volatile ConnectorRequestReplicator requestReplicator;
private volatile SecretsManager secretsManager;
+ private volatile ConnectorAssetRepository assetRepository;
@Override
public void initialize(final ConnectorRepositoryInitializationContext context) {
this.extensionManager = context.getExtensionManager();
this.requestReplicator = context.getRequestReplicator();
this.secretsManager = context.getSecretsManager();
+ this.assetRepository = new StandardConnectorAssetRepository(context.getAssetManager());
}
@Override
@@ -223,4 +225,8 @@ public class StandardConnectorRepository implements ConnectorRepository {
return new StandardConnectorInitializationContext.Builder();
}
+ @Override
+ public ConnectorAssetRepository getAssetRepository() {
+ return assetRepository;
+ }
}
\ No newline at end of file
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index fd6cff90c8..5b29dcc34a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -276,19 +276,19 @@ public class ExtensionBuilder {
return this;
}
- public ExtensionBuilder connectorValidationTrigger(final ConnectorValidationTrigger connectorValidationTrigger) {
- this.connectorValidationTrigger = connectorValidationTrigger;
- return this;
- }
-
- public ProcessorNode buildProcessor() {
- requireNonNull(identifier, "Processor ID");
- requireNonNull(type, "Processor Type");
- requireNonNull(bundleCoordinate, "Bundle Coordinate");
- requireNonNull(extensionManager, "Extension Manager");
- requireNonNull(serviceProvider, "Controller Service Provider");
- requireNonNull(nodeTypeProvider, "Node Type Provider");
- requireNonNull(reloadComponent, "Reload Component");
+ public ExtensionBuilder connectorValidationTrigger(final ConnectorValidationTrigger connectorValidationTrigger) {
+ this.connectorValidationTrigger = connectorValidationTrigger;
+ return this;
+ }
+
+ public ProcessorNode buildProcessor() {
+ requireNonNull(identifier, "Processor ID");
+ requireNonNull(type, "Processor Type");
+ requireNonNull(bundleCoordinate, "Bundle Coordinate");
+ requireNonNull(extensionManager, "Extension Manager");
+ requireNonNull(serviceProvider, "Controller Service Provider");
+ requireNonNull(nodeTypeProvider, "Node Type Provider");
+ requireNonNull(reloadComponent, "Reload Component");
boolean creationSuccessful = true;
final StandardLoggingContext loggingContext = new StandardLoggingContext();
@@ -519,19 +519,19 @@ public class ExtensionBuilder {
}
};
- final ConnectorNode connectorNode = new StandardConnectorNode(
- identifier,
- flowController.getFlowManager(),
- extensionManager,
- connectorsAuthorizable,
- connectorDetails,
- componentType,
- type,
- activeConfigurationContext,
- connectorStateTransition,
- flowContextFactory,
- connectorValidationTrigger
- );
+ final ConnectorNode connectorNode = new StandardConnectorNode(
+ identifier,
+ flowController.getFlowManager(),
+ extensionManager,
+ connectorsAuthorizable,
+ connectorDetails,
+ componentType,
+ type,
+ activeConfigurationContext,
+ connectorStateTransition,
+ flowContextFactory,
+ connectorValidationTrigger
+ );
initializeDefaultValues(connector, connectorNode.getActiveFlowContext());
// TODO: If an Exception is thrown in the call to #initialize, we should create a Ghosted Connector
@@ -568,7 +568,7 @@ public class ExtensionBuilder {
.name(name)
.componentLog(componentLog)
.secretsManager(flowController.getConnectorRepository().getSecretsManager())
- .assetManager(flowController.getAssetManager())
+ .assetManager(flowController.getConnectorAssetManager())
.build();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index 8464c58d63..81a7115ada 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -27,6 +27,7 @@ import org.apache.nifi.asset.AssetReferenceLookup;
import org.apache.nifi.asset.StandardAssetManager;
import org.apache.nifi.asset.StandardAssetManagerInitializationContext;
import org.apache.nifi.asset.StandardAssetReferenceLookup;
+import org.apache.nifi.asset.StandardConnectorAssetManager;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
@@ -228,6 +229,8 @@ import org.apache.nifi.web.revision.RevisionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.management.NotificationEmitter;
+import javax.net.ssl.SSLContext;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
@@ -260,8 +263,6 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import java.util.stream.Collectors;
-import javax.management.NotificationEmitter;
-import javax.net.ssl.SSLContext;
import static java.util.Objects.requireNonNull;
@@ -274,6 +275,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
public static final String DEFAULT_PROVENANCE_REPO_IMPLEMENTATION = "org.apache.nifi.provenance.VolatileProvenanceRepository";
public static final String DEFAULT_SWAP_MANAGER_IMPLEMENTATION = "org.apache.nifi.controller.FileSystemSwapManager";
public static final String DEFAULT_ASSET_MANAGER_IMPLEMENTATION = StandardAssetManager.class.getName();
+ public static final String DEFAULT_CONNECTOR_ASSET_MANAGER_IMPLEMENTATION = StandardConnectorAssetManager.class.getName();
public static final String DEFAULT_CONNECTOR_REPOSITORY_IMPLEMENTATION = StandardConnectorRepository.class.getName();
public static final String DEFAULT_SECRETS_MANAGER_IMPLEMENTATION = ParameterProviderSecretsManager.class.getName();
@@ -291,6 +293,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
private final ProvenanceRepository provenanceRepository;
private final BulletinRepository bulletinRepository;
private final AssetManager assetManager;
+ private final AssetManager connectorAssetManager;
private final LifecycleStateManager lifecycleStateManager;
private final StandardProcessScheduler processScheduler;
private final SnippetManager snippetManager;
@@ -584,7 +587,19 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
final long maxAppendableBytes = getMaxAppendableBytes();
repositoryContextFactory = new RepositoryContextFactory(contentRepository, flowFileRepository, flowFileEventRepository,
counterRepositoryRef.get(), provenanceRepository, stateManagerProvider, maxAppendableBytes);
- assetManager = createAssetManager(nifiProperties);
+
+ assetManager = createAssetManager(
+ nifiProperties,
+ NiFiProperties.ASSET_MANAGER_IMPLEMENTATION,
+ NiFiProperties.ASSET_MANAGER_PREFIX,
+ DEFAULT_ASSET_MANAGER_IMPLEMENTATION
+ );
+ connectorAssetManager = createAssetManager(
+ nifiProperties,
+ NiFiProperties.CONNECTOR_ASSET_MANAGER_IMPLEMENTATION,
+ NiFiProperties.CONNECTOR_ASSET_MANAGER_PREFIX,
+ DEFAULT_CONNECTOR_ASSET_MANAGER_IMPLEMENTATION
+ );
this.flowAnalysisThreadPool = new FlowEngine(1, "Background Flow Analysis", true);
if (ruleViolationsManager != null) {
@@ -612,7 +627,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
controllerServiceProvider, new StandardControllerServiceApiLookup(extensionManager));
final SecretsManager secretsManager = createSecretsManager(nifiProperties, extensionManager, flowManager);
- connectorRepository = createConnectorRepository(nifiProperties, extensionManager, flowManager, secretsManager, this, connectorRequestReplicator);
+ connectorRepository = createConnectorRepository(nifiProperties, extensionManager, flowManager, connectorAssetManager, secretsManager, this, connectorRequestReplicator);
final PythonBridge rawPythonBridge = createPythonBridge(nifiProperties, controllerServiceProvider);
final ClassLoader pythonBridgeClassLoader = rawPythonBridge.getClass().getClassLoader();
@@ -886,7 +901,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
}
private static ConnectorRepository createConnectorRepository(final NiFiProperties properties, final ExtensionDiscoveringManager extensionManager, final FlowManager flowManager,
- final SecretsManager secretsManager, final NodeTypeProvider nodeTypeProvider, final ConnectorRequestReplicator requestReplicator) {
+ final AssetManager assetManager, final SecretsManager secretsManager, final NodeTypeProvider nodeTypeProvider, final ConnectorRequestReplicator requestReplicator) {
final String implementationClassName = properties.getProperty(NiFiProperties.CONNECTOR_REPOSITORY_IMPLEMENTATION, DEFAULT_CONNECTOR_REPOSITORY_IMPLEMENTATION);
@@ -902,6 +917,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
flowManager,
extensionManager,
secretsManager,
+ assetManager,
nodeTypeProvider,
requestReplicator
);
@@ -1458,14 +1474,14 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
}
}
- private AssetManager createAssetManager(final NiFiProperties properties) {
- final String implementationClassName = properties.getProperty(NiFiProperties.ASSET_MANAGER_IMPLEMENTATION, DEFAULT_ASSET_MANAGER_IMPLEMENTATION);
+ private AssetManager createAssetManager(final NiFiProperties properties, final String implementationClassProperty, final String propertyPrefix, final String defaultImplementationClass) {
+ final String implementationClassName = properties.getProperty(implementationClassProperty, defaultImplementationClass);
try {
final AssetManager assetManager = NarThreadContextClassLoader.createInstance(extensionManager, implementationClassName, AssetManager.class, properties);
final AssetReferenceLookup assetReferenceLookup = new StandardAssetReferenceLookup(parameterContextManager);
- final Map<String, String> relevantNiFiProperties = properties.getPropertiesWithPrefix(NiFiProperties.ASSET_MANAGER_PREFIX);
- final int prefixLength = NiFiProperties.ASSET_MANAGER_PREFIX.length();
+ final Map<String, String> relevantNiFiProperties = properties.getPropertiesWithPrefix(propertyPrefix);
+ final int prefixLength = propertyPrefix.length();
final Map<String, String> assetManagerProperties = relevantNiFiProperties.entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey().substring(prefixLength), Map.Entry::getValue));
@@ -1483,9 +1499,16 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
}
@Override
- public Asset createAsset(final String parameterContextId, final String assetName, final InputStream contents) throws IOException {
+ public Asset createAsset(final String ownerId, final String assetName, final InputStream contents) throws IOException {
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(assetManagerClassLoader)) {
- return assetManager.createAsset(parameterContextId, assetName, contents);
+ return assetManager.createAsset(ownerId, assetName, contents);
+ }
+ }
+
+ @Override
+ public Asset saveAsset(final String ownerId, final String assetId, final String assetName, final InputStream contents) throws IOException {
+ try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(assetManagerClassLoader)) {
+ return assetManager.saveAsset(ownerId, assetId, assetName, contents);
}
}
@@ -1497,16 +1520,16 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
}
@Override
- public List<Asset> getAssets(final String parameterContextId) {
+ public List<Asset> getAssets(final String ownerId) {
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(assetManagerClassLoader)) {
- return assetManager.getAssets(parameterContextId);
+ return assetManager.getAssets(ownerId);
}
}
@Override
- public Asset createMissingAsset(final String parameterContextId, final String assetName) {
+ public Asset createMissingAsset(final String ownerId, final String assetName) {
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(assetManagerClassLoader)) {
- return assetManager.createMissingAsset(parameterContextId, assetName);
+ return assetManager.createMissingAsset(ownerId, assetName);
}
}
@@ -1530,6 +1553,10 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
return assetManager;
}
+ public AssetManager getConnectorAssetManager() {
+ return connectorAssetManager;
+ }
+
private ProvenanceRepository createProvenanceRepository(final NiFiProperties properties) {
final String implementationClassName = properties.getProperty(NiFiProperties.PROVENANCE_REPO_IMPLEMENTATION_CLASS, DEFAULT_PROVENANCE_REPO_IMPLEMENTATION);
if (StringUtils.isBlank(implementationClassName)) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java
index 5de3486164..4c021429d1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java
@@ -122,7 +122,8 @@ public class StandardFlowService implements FlowService, ProtocolHandler {
private final ClusterCoordinator clusterCoordinator;
private final RevisionManager revisionManager;
private final NarManager narManager;
- private final AssetSynchronizer assetSynchronizer;
+ private final AssetSynchronizer parameterContextAssetSynchronizer;
+ private final AssetSynchronizer connectorAssetSynchronizer;
private volatile SaveReportingTask saveReportingTask;
/**
@@ -153,10 +154,12 @@ public class StandardFlowService implements FlowService, ProtocolHandler {
final NiFiProperties nifiProperties,
final RevisionManager revisionManager,
final NarManager narManager,
- final AssetSynchronizer assetSynchronizer,
+ final AssetSynchronizer parameterContextAssetSynchronizer,
+ final AssetSynchronizer connectorAssetSynchronizer,
final Authorizer authorizer) throws IOException {
- return new StandardFlowService(controller, nifiProperties, null, false, null, revisionManager, narManager, assetSynchronizer, authorizer);
+ return new StandardFlowService(controller, nifiProperties, null, false, null, revisionManager, narManager,
+ parameterContextAssetSynchronizer, connectorAssetSynchronizer, authorizer);
}
public static StandardFlowService createClusteredInstance(
@@ -166,10 +169,12 @@ public class StandardFlowService implements FlowService, ProtocolHandler {
final ClusterCoordinator coordinator,
final RevisionManager revisionManager,
final NarManager narManager,
- final AssetSynchronizer assetSynchronizer,
+ final AssetSynchronizer parameterContextAssetSynchronizer,
+ final AssetSynchronizer connectorAssetSynchronizer,
final Authorizer authorizer) throws IOException {
- return new StandardFlowService(controller, nifiProperties, senderListener, true, coordinator, revisionManager, narManager, assetSynchronizer, authorizer);
+ return new StandardFlowService(controller, nifiProperties, senderListener, true, coordinator, revisionManager,
+ narManager, parameterContextAssetSynchronizer, connectorAssetSynchronizer, authorizer);
}
private StandardFlowService(
@@ -180,7 +185,8 @@ public class StandardFlowService implements FlowService, ProtocolHandler {
final ClusterCoordinator clusterCoordinator,
final RevisionManager revisionManager,
final NarManager narManager,
- final AssetSynchronizer assetSynchronizer,
+ final AssetSynchronizer parameterContextAssetSynchronizer,
+ final AssetSynchronizer connectorAssetSynchronizer,
final Authorizer authorizer) throws IOException {
this.nifiProperties = nifiProperties;
@@ -196,7 +202,8 @@ public class StandardFlowService implements FlowService, ProtocolHandler {
}
this.revisionManager = revisionManager;
this.narManager = narManager;
- this.assetSynchronizer = assetSynchronizer;
+ this.parameterContextAssetSynchronizer = parameterContextAssetSynchronizer;
+ this.connectorAssetSynchronizer = connectorAssetSynchronizer;
this.authorizer = authorizer;
if (configuredForClustering) {
@@ -913,8 +920,13 @@ public class StandardFlowService implements FlowService, ProtocolHandler {
// load new controller state
loadFromBytes(dataFlow, true, BundleUpdateStrategy.USE_SPECIFIED_OR_COMPATIBLE_OR_GHOST);
- // sync assets after loading the flow so that parameter contexts exist first
- assetSynchronizer.synchronize();
+ // sync assets after loading the flow so that parameter contexts and connectors exist first
+ if (parameterContextAssetSynchronizer != null) {
+ parameterContextAssetSynchronizer.synchronize();
+ }
+ if (connectorAssetSynchronizer != null) {
+ connectorAssetSynchronizer.synchronize();
+ }
// set node ID on controller before we start heartbeating because heartbeat needs node ID
clusterCoordinator.setLocalNodeIdentifier(nodeId);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 398cb085bc..c48288bd71 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -765,7 +765,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
final ConnectorRepository connectorRepository = flowController.getConnectorRepository();
final ConnectorStateTransition stateTransition = connectorRepository.createStateTransition(type, id);
final StandardConnectorConfigurationContext activeConfigurationContext = new StandardConnectorConfigurationContext(
- flowController.getAssetManager(), flowController.getConnectorRepository().getSecretsManager());
+ flowController.getConnectorAssetManager(), flowController.getConnectorRepository().getSecretsManager());
final ProcessGroupFactory processGroupFactory = groupId -> createProcessGroup(groupId, false);
final FlowContextFactory flowContextFactory = new FlowControllerFlowContextFactory(flowController, managedRootGroup, activeConfigurationContext, processGroupFactory);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
index 339c9a5714..197800c470 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
@@ -1143,7 +1143,7 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
return switch (existingReference) {
case StringLiteralValue stringLiteral -> Objects.equals(stringLiteral.getValue(), versionedReference.getValue());
- case AssetReference assetRef -> Objects.equals(assetRef.getAssetIdentifier(), versionedReference.getAssetId());
+ case AssetReference assetRef -> Objects.equals(assetRef.getAssetIdentifiers(), versionedReference.getAssetIds());
case SecretReference secretRef -> Objects.equals(secretRef.getProviderId(), versionedReference.getProviderId())
&& Objects.equals(secretRef.getSecretName(), versionedReference.getSecretName());
};
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
index c4a123a691..cd17fc0349 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
@@ -25,6 +25,7 @@ import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.asset.AssetSynchronizer;
import org.apache.nifi.asset.StandardAssetComponentManager;
import org.apache.nifi.asset.StandardAssetSynchronizer;
+import org.apache.nifi.asset.StandardConnectorAssetSynchronizer;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.cluster.coordination.ClusterCoordinator;
import org.apache.nifi.cluster.coordination.heartbeat.HeartbeatMonitor;
@@ -254,7 +255,8 @@ public class FlowControllerConfiguration {
properties,
revisionManager,
narManager,
- assetSynchronizer(),
+ parameterContextAssetSynchronizer(),
+ connectorAssetSynchronizer(),
authorizer
);
} else {
@@ -265,7 +267,8 @@ public class FlowControllerConfiguration {
clusterCoordinator,
revisionManager,
narManager,
- assetSynchronizer(),
+ parameterContextAssetSynchronizer(),
+ connectorAssetSynchronizer(),
authorizer
);
}
@@ -454,7 +457,7 @@ public class FlowControllerConfiguration {
}
/**
- * Asset Synchronizer depends on ClusterCoordinator, WebClientService, and NiFiProperties
+ * Parameter Conext Asset Synchronizer depends on ClusterCoordinator, WebClientService, and NiFiProperties
*
* @return Asset Synchronizer
*/
@@ -463,6 +466,21 @@ public class FlowControllerConfiguration {
return new StandardAssetSynchronizer(flowController(), clusterCoordinator, webClientService(), properties, affectedComponentManager());
}
+ @Bean
+ public AssetSynchronizer parameterContextAssetSynchronizer() throws Exception {
+ return new StandardAssetSynchronizer(flowController(), clusterCoordinator, webClientService(), properties);
+ }
+
+ /**
+ * Connector Asset Synchronizer depends on ClusterCoordinator, WebClientService, and NiFiProperties
+ *
+ * @return Connector Asset Synchronizer
+ */
+ @Bean
+ public AssetSynchronizer connectorAssetSynchronizer() throws Exception {
+ return new StandardConnectorAssetSynchronizer(flowController(), clusterCoordinator, webClientService(), properties);
+ }
+
/**
* Affected Component Manager depends on FlowController
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorInitializationContext.java
index 8b1cae7311..ae15efa509 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorInitializationContext.java
@@ -1,3 +1,19 @@
+/*
+ * 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.components.connector;
import org.apache.nifi.components.connector.components.ParameterValue;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-headless-server/src/main/java/org/apache/nifi/headless/HeadlessNiFiServer.java b/nifi-framework-bundle/nifi-framework/nifi-headless-server/src/main/java/org/apache/nifi/headless/HeadlessNiFiServer.java
index 5da63ec84e..96056266e3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-headless-server/src/main/java/org/apache/nifi/headless/HeadlessNiFiServer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-headless-server/src/main/java/org/apache/nifi/headless/HeadlessNiFiServer.java
@@ -156,7 +156,8 @@ public class HeadlessNiFiServer implements NiFiServer {
props,
null, // revision manager
null, // NAR Manager
- null, // Asset Synchronizer
+ null, // Parameter Context Asset Synchronizer
+ null, // Connector Asset Synchronizer
authorizer);
diagnosticsFactory = new BootstrapDiagnosticsFactory();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
index e26e0d2aa3..73922f984a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
@@ -32,6 +32,7 @@ import org.apache.nifi.components.connector.NamedStepConfiguration;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
+import org.apache.nifi.web.api.dto.AssetReferenceDTO;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
@@ -49,6 +50,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Audits connector creation/removal and configuration changes.
@@ -275,7 +278,7 @@ public class ConnectorAuditor extends NiFiAuditor {
return switch (valueRef) {
case StringLiteralValue stringLiteral -> stringLiteral.getValue();
- case AssetReference assetRef -> "asset:" + assetRef.getAssetIdentifier();
+ case AssetReference assetRef -> "assets:" + assetRef.getAssetIdentifiers();
case SecretReference secretRef -> "secret:" + secretRef.getProviderId() + "/" + secretRef.getProviderName() + "/" + secretRef.getSecretName();
};
}
@@ -295,7 +298,10 @@ public class ConnectorAuditor extends NiFiAuditor {
if (valueType == null || "STRING_LITERAL".equals(valueType)) {
return valueRefDto.getValue();
} else if ("ASSET_REFERENCE".equals(valueType)) {
- return "asset:" + valueRefDto.getAssetIdentifier();
+ return "assets:" + Stream.ofNullable(valueRefDto.getAssetReferences())
+ .flatMap(List::stream)
+ .map(AssetReferenceDTO::getId)
+ .collect(Collectors.joining(","));
} else if ("SECRET_REFERENCE".equals(valueType)) {
return "secret:" + valueRefDto.getSecretProviderId() + "/" + valueRefDto.getSecretProviderName() + "/" + valueRefDto.getSecretName();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 7e8a53c8a1..b10cf18a45 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -17,6 +17,7 @@
package org.apache.nifi.web;
import io.prometheus.client.CollectorRegistry;
+import org.apache.nifi.asset.Asset;
import org.apache.nifi.authorization.AuthorizeAccess;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.user.NiFiUser;
@@ -57,7 +58,9 @@ import org.apache.nifi.web.api.dto.ClusterDTO;
import org.apache.nifi.web.api.dto.ComponentHistoryDTO;
import org.apache.nifi.web.api.dto.ComponentStateDTO;
import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
import org.apache.nifi.web.api.dto.ControllerConfigurationDTO;
import org.apache.nifi.web.api.dto.ControllerDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
@@ -82,8 +85,6 @@ import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
-import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
-import org.apache.nifi.web.api.dto.ConnectorDTO;
import org.apache.nifi.web.api.dto.ResourceDTO;
import org.apache.nifi.web.api.dto.SnippetDTO;
import org.apache.nifi.web.api.dto.SystemDiagnosticsDTO;
@@ -108,9 +109,13 @@ import org.apache.nifi.web.api.entity.ClearBulletinsForGroupResultsEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsResultEntity;
import org.apache.nifi.web.api.entity.ComponentValidationResultEntity;
import org.apache.nifi.web.api.entity.ConfigurationAnalysisEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ConnectionEntity;
import org.apache.nifi.web.api.entity.ConnectionStatisticsEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ControllerBulletinsEntity;
import org.apache.nifi.web.api.entity.ControllerConfigurationEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
@@ -150,10 +155,6 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
-import org.apache.nifi.web.api.entity.ConnectorEntity;
-import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
-import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
-import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
import org.apache.nifi.web.api.entity.SnippetEntity;
import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
@@ -177,6 +178,7 @@ import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -215,6 +217,8 @@ public interface NiFiServiceFacade {
ConnectorEntity applyConnectorUpdate(Revision revision, String connectorId);
+ ConnectorEntity discardConnectorUpdate(Revision revision, String connectorId);
+
ProcessGroupFlowEntity getConnectorFlow(String id, boolean uiOnly);
ProcessGroupStatusEntity getConnectorProcessGroupStatus(String id, Boolean recursive);
@@ -227,6 +231,14 @@ public interface NiFiServiceFacade {
ConnectorPropertyAllowableValuesEntity getConnectorPropertyAllowableValues(String connectorId, String stepName, String groupName, String propertyName, String filter);
+ void verifyCreateConnectorAsset(String connectorId);
+
+ AssetEntity createConnectorAsset(String connectorId, String assetId, String assetName, InputStream content) throws IOException;
+
+ List<AssetEntity> getConnectorAssets(String connectorId);
+
+ Optional<Asset> getConnectorAsset(String assetId);
+
// ----------------------------------------
// Synchronization methods
// ----------------------------------------
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 6fb12571f2..f9309f931d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -247,7 +247,9 @@ import org.apache.nifi.web.api.dto.ComponentStateDTO;
import org.apache.nifi.web.api.dto.ComponentValidationResultDTO;
import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ConfigurationAnalysisDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
import org.apache.nifi.web.api.dto.ControllerConfigurationDTO;
import org.apache.nifi.web.api.dto.ControllerDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
@@ -259,8 +261,6 @@ import org.apache.nifi.web.api.dto.DocumentedTypeDTO;
import org.apache.nifi.web.api.dto.DropRequestDTO;
import org.apache.nifi.web.api.dto.DtoFactory;
import org.apache.nifi.web.api.dto.EntityFactory;
-import org.apache.nifi.web.api.dto.ConnectorDTO;
-import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.FlowAnalysisRuleDTO;
import org.apache.nifi.web.api.dto.FlowAnalysisRuleViolationDTO;
import org.apache.nifi.web.api.dto.FlowConfigurationDTO;
@@ -332,11 +332,10 @@ import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
import org.apache.nifi.web.api.dto.status.StatusSnapshotDTO;
import org.apache.nifi.web.api.entity.AccessPolicyEntity;
import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
-import org.apache.nifi.web.api.entity.AllowableValueEntity;
-import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.AffectedComponentEntity;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.apache.nifi.web.api.entity.AssetEntity;
import org.apache.nifi.web.api.entity.BulletinEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsForGroupResultsEntity;
@@ -344,9 +343,13 @@ import org.apache.nifi.web.api.entity.ClearBulletinsResultEntity;
import org.apache.nifi.web.api.entity.ComponentReferenceEntity;
import org.apache.nifi.web.api.entity.ComponentValidationResultEntity;
import org.apache.nifi.web.api.entity.ConfigurationAnalysisEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ConnectionEntity;
import org.apache.nifi.web.api.entity.ConnectionStatisticsEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ControllerBulletinsEntity;
import org.apache.nifi.web.api.entity.ControllerConfigurationEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
@@ -391,9 +394,6 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
-import org.apache.nifi.web.api.entity.ConnectorEntity;
-import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
-import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.SnippetEntity;
@@ -410,10 +410,10 @@ import org.apache.nifi.web.api.entity.VersionedFlowSnapshotMetadataEntity;
import org.apache.nifi.web.api.entity.VersionedReportingTaskImportResponseEntity;
import org.apache.nifi.web.api.request.FlowMetricsRegistry;
import org.apache.nifi.web.api.request.FlowMetricsReportingStrategy;
-import org.apache.nifi.web.dao.ConnectorDAO;
import org.apache.nifi.web.controller.ControllerFacade;
import org.apache.nifi.web.dao.AccessPolicyDAO;
import org.apache.nifi.web.dao.ConnectionDAO;
+import org.apache.nifi.web.dao.ConnectorDAO;
import org.apache.nifi.web.dao.ControllerServiceDAO;
import org.apache.nifi.web.dao.FlowAnalysisRuleDAO;
import org.apache.nifi.web.dao.FlowRegistryDAO;
@@ -3667,6 +3667,26 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
}
+ @Override
+ public ConnectorEntity discardConnectorUpdate(final Revision revision, final String connectorId) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ connectorDAO.discardWorkingConfiguration(connectorId);
+
+ final ConnectorNode node = connectorDAO.getConnector(connectorId);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(node));
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
+ }
+
@Override
public ProcessGroupFlowEntity getConnectorFlow(final String id, final boolean uiOnly) {
final ConnectorNode connectorNode = connectorDAO.getConnector(id);
@@ -3730,6 +3750,29 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return entityFactory.createConnectorPropertyAllowableValuesEntity(stepName, groupName, propertyName, allowableValueEntities);
}
+ @Override
+ public void verifyCreateConnectorAsset(final String connectorId) {
+ connectorDAO.verifyCreateAsset(connectorId);
+ }
+
+ @Override
+ public AssetEntity createConnectorAsset(final String connectorId, final String assetId, final String assetName, final InputStream content) throws IOException {
+ final Asset createdAsset = connectorDAO.createAsset(connectorId, assetId, assetName, content);
+ return dtoFactory.createAssetEntity(createdAsset);
+ }
+
+ @Override
+ public List<AssetEntity> getConnectorAssets(final String connectorId) {
+ return connectorDAO.getAssets(connectorId).stream()
+ .map(dtoFactory::createAssetEntity)
+ .toList();
+ }
+
+ @Override
+ public Optional<Asset> getConnectorAsset(final String assetId) {
+ return connectorDAO.getAsset(assetId);
+ }
+
@Override
public ReportingTaskEntity updateReportingTask(final Revision revision, final ReportingTaskDTO reportingTaskDTO) {
// get the component, ensure we have access to it, and perform the update request
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
index e5ad85ea1a..0112d7e61c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
@@ -28,6 +28,7 @@ import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
@@ -36,27 +37,50 @@ import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.StreamingOutput;
import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.asset.Asset;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.OperationAuthorizable;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.cluster.coordination.ClusterCoordinator;
+import org.apache.nifi.cluster.coordination.http.replication.RequestReplicationHeader;
+import org.apache.nifi.cluster.coordination.http.replication.UploadRequest;
+import org.apache.nifi.cluster.coordination.http.replication.UploadRequestReplicator;
+import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.controller.ScheduledState;
+import org.apache.nifi.processor.DataUnit;
+import org.apache.nifi.stream.io.MaxLengthInputStream;
import org.apache.nifi.ui.extension.UiExtension;
import org.apache.nifi.ui.extension.UiExtensionMapping;
+import org.apache.nifi.util.file.FileUtils;
import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.Revision;
import org.apache.nifi.web.UiExtensionType;
-import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.concurrent.AsyncRequestManager;
+import org.apache.nifi.web.api.concurrent.AsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.RequestManager;
+import org.apache.nifi.web.api.concurrent.StandardAsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.StandardUpdateStep;
+import org.apache.nifi.web.api.concurrent.UpdateStep;
+import org.apache.nifi.web.api.dto.AssetDTO;
import org.apache.nifi.web.api.dto.BundleDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.dto.VerifyConnectorConfigStepRequestDTO;
import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
+import org.apache.nifi.web.api.entity.AssetEntity;
+import org.apache.nifi.web.api.entity.AssetsEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ConnectorEntity;
@@ -66,26 +90,24 @@ import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.SearchResultsEntity;
import org.apache.nifi.web.api.entity.SecretsEntity;
-import org.apache.nifi.web.api.concurrent.AsyncRequestManager;
-import org.apache.nifi.web.api.concurrent.AsynchronousWebRequest;
-import org.apache.nifi.web.api.concurrent.RequestManager;
-import org.apache.nifi.web.api.concurrent.StandardAsynchronousWebRequest;
-import org.apache.nifi.web.api.concurrent.StandardUpdateStep;
-import org.apache.nifi.web.api.concurrent.UpdateStep;
-import org.apache.nifi.web.api.dto.VerifyConnectorConfigStepRequestDTO;
import org.apache.nifi.web.api.entity.VerifyConnectorConfigStepRequestEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.nifi.web.api.request.LongParameter;
+import org.apache.nifi.web.client.api.HttpResponseStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.function.Consumer;
/**
@@ -98,10 +120,16 @@ public class ConnectorResource extends ApplicationResource {
private static final Logger logger = LoggerFactory.getLogger(ConnectorResource.class);
private static final String VERIFICATION_REQUEST_TYPE = "verification-request";
+ private static final String FILENAME_HEADER = "Filename";
+ private static final String CONTENT_TYPE_HEADER = "Content-Type";
+ private static final String UPLOAD_CONTENT_TYPE = "application/octet-stream";
+ private static final long MAX_ASSET_SIZE_BYTES = (long) DataUnit.GB.toB(1);
private NiFiServiceFacade serviceFacade;
private Authorizer authorizer;
private FlowResource flowResource;
+ private UploadRequestReplicator uploadRequestReplicator;
+
private final RequestManager<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>> configVerificationRequestManager =
new AsyncRequestManager<>(100, 1L, "Connector Configuration Step Verification");
@@ -1242,6 +1270,83 @@ public class ConnectorResource extends ApplicationResource {
);
}
+ /**
+ * Discards the working configuration of a connector, reverting to the last applied configuration.
+ *
+ * @param version The revision is used to verify the client is working with the latest version of the flow.
+ * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response.
+ * @param id The id of the connector whose working configuration should be discarded.
+ * @return A connectorEntity.
+ */
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/working-configuration")
+ @Operation(
+ summary = "Discards the working configuration of a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "This will discard any pending configuration changes for the connector and revert to the last applied configuration.",
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid}")
+ }
+ )
+ public Response discardConnectorUpdate(
+ @Parameter(
+ description = "The revision is used to verify the client is working with the latest version of the flow."
+ )
+ @QueryParam(VERSION) final LongParameter version,
+ @Parameter(
+ description = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response."
+ )
+ @QueryParam(CLIENT_ID) final ClientIdParameter clientId,
+ @Parameter(
+ description = "Acknowledges that this node is disconnected to allow for mutable requests to proceed."
+ )
+ @QueryParam(DISCONNECTED_NODE_ACKNOWLEDGED) @DefaultValue("false") final Boolean disconnectedNodeAcknowledged,
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.DELETE);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(disconnectedNodeAcknowledged);
+ }
+
+ final ConnectorEntity requestConnectorEntity = new ConnectorEntity();
+ requestConnectorEntity.setId(id);
+
+ final Revision requestRevision = new Revision(version == null ? null : version.getLong(), clientId.getClientId(), id);
+ return withWriteLock(
+ serviceFacade,
+ requestConnectorEntity,
+ requestRevision,
+ lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+ },
+ () -> {
+ // Verify the connector exists
+ serviceFacade.getConnector(id);
+ },
+ (revision, connectorEntity) -> {
+ final ConnectorEntity entity = serviceFacade.discardConnectorUpdate(revision, id);
+ populateRemainingConnectorEntityContent(entity);
+
+ return generateOkResponse(entity).build();
+ }
+ );
+ }
+
/**
* Retrieves the flow for the process group managed by the specified connector.
*
@@ -1380,6 +1485,204 @@ public class ConnectorResource extends ApplicationResource {
return generateOkResponse(entity).build();
}
+ @POST
+ @Consumes(MediaType.APPLICATION_OCTET_STREAM)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}/assets")
+ @Operation(
+ summary = "Creates a new Asset in the given Connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = AssetEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "This endpoint will create a new Asset in the Connector. The Asset will be created with the given name and the contents of the file that is uploaded.",
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid}")
+ }
+ )
+ public Response createAsset(
+ @PathParam("id") final String connectorId,
+ @HeaderParam(FILENAME_HEADER) final String assetName,
+ @Parameter(description = "The contents of the asset", required = true) final InputStream assetContents) throws IOException {
+
+ // Validate input
+ if (StringUtils.isBlank(assetName)) {
+ throw new IllegalArgumentException(FILENAME_HEADER + " header is required");
+ }
+ if (assetContents == null) {
+ throw new IllegalArgumentException("Asset contents must be specified.");
+ }
+
+ final String sanitizedAssetName = FileUtils.getSanitizedFilename(assetName);
+ if (!assetName.equals(sanitizedAssetName)) {
+ throw new IllegalArgumentException(FILENAME_HEADER + " header contains an invalid file name");
+ }
+
+ // If clustered and not all nodes are connected, do not allow creating an asset.
+ // Generally, we allow the flow to be modified when nodes are disconnected, but we do not allow creating an asset because
+ // the cluster has no mechanism for synchronizing those assets after the upload.
+ final ClusterCoordinator clusterCoordinator = getClusterCoordinator();
+ if (clusterCoordinator != null) {
+ final Set<NodeIdentifier> disconnectedNodes = clusterCoordinator.getNodeIdentifiers(NodeConnectionState.CONNECTING, NodeConnectionState.DISCONNECTED, NodeConnectionState.DISCONNECTING);
+ if (!disconnectedNodes.isEmpty()) {
+ throw new IllegalStateException("Cannot create an Asset because the following %s nodes are not currently connected: %s".formatted(disconnectedNodes.size(), disconnectedNodes));
+ }
+ }
+
+ final NiFiUser currentUser = NiFiUserUtils.getNiFiUser();
+
+ // Verify Connector exists
+ serviceFacade.verifyCreateConnectorAsset(connectorId);
+
+ // Authorize the request
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(connectorId);
+ connector.authorize(authorizer, RequestAction.WRITE, currentUser);
+ });
+
+ // If we need to replicate the request, we do so using the Upload Request Replicator, rather than the typical replicate() method.
+ // This is because Upload Request Replication works differently in that it needs to be able to replicate the InputStream multiple times,
+ // so it must create a file on disk to do so and then use the file's content to replicate the request. It also bypasses the two-phase
+ // commit process that is used for other requests because doing so would result in uploading the file twice to each node or providing a
+ // different request for each of the two phases.
+
+ final long startTime = System.currentTimeMillis();
+ final InputStream maxLengthInputStream = new MaxLengthInputStream(assetContents, MAX_ASSET_SIZE_BYTES);
+
+ final AssetEntity assetEntity;
+ if (isReplicateRequest()) {
+ final String uploadRequestId = UUID.randomUUID().toString();
+ final UploadRequest<AssetEntity> uploadRequest = new UploadRequest.Builder<AssetEntity>()
+ .user(NiFiUserUtils.getNiFiUser())
+ .filename(sanitizedAssetName)
+ .identifier(uploadRequestId)
+ .contents(maxLengthInputStream)
+ .header(FILENAME_HEADER, sanitizedAssetName)
+ .header(CONTENT_TYPE_HEADER, UPLOAD_CONTENT_TYPE)
+ .header(RequestReplicationHeader.CLUSTER_ID_GENERATION_SEED.getHeader(), uploadRequestId)
+ .exampleRequestUri(getAbsolutePath())
+ .responseClass(AssetEntity.class)
+ .successfulResponseStatus(HttpResponseStatus.OK.getCode())
+ .build();
+ assetEntity = uploadRequestReplicator.upload(uploadRequest);
+ } else {
+ final String assetId = generateUuid();
+ logger.info("Creating asset [id={},name={}] in Connector [{}]", assetId, sanitizedAssetName, connectorId);
+ assetEntity = serviceFacade.createConnectorAsset(connectorId, assetId, sanitizedAssetName, maxLengthInputStream);
+
+ final AssetDTO assetDTO = assetEntity.getAsset();
+ final long elapsedTime = System.currentTimeMillis() - startTime;
+ logger.info("Creation of asset [id={},name={}] in Connector [{}] completed in {} ms", assetDTO.getId(), assetDTO.getName(), connectorId, elapsedTime);
+ }
+
+ return generateOkResponse(assetEntity).build();
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}/assets")
+ @Operation(
+ summary = "Lists the assets that belong to the Connector with the given ID",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = AssetsEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "Lists the assets that belong to the Connector with the given ID.",
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ }
+ )
+ public Response getAssets(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String connectorId
+ ) {
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(connectorId);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final List<AssetEntity> connectorAssets = serviceFacade.getConnectorAssets(connectorId);
+ logger.debug("Returning [{}] assets for connector [{}]", connectorAssets.size(), connectorId);
+
+ final AssetsEntity assetsEntity = new AssetsEntity();
+ assetsEntity.setAssets(connectorAssets);
+
+ return generateOkResponse(assetsEntity).build();
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_OCTET_STREAM)
+ @Path("{id}/assets/{assetId}")
+ @Operation(
+ summary = "Retrieves the content of the asset with the given id for a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = byte[].class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ }
+ )
+ public Response getAssetContent(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String connectorId,
+ @Parameter(
+ description = "The asset id.",
+ required = true
+ )
+ @PathParam("assetId") final String assetId
+ ) {
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(connectorId);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ final Asset asset = serviceFacade.getConnectorAsset(assetId)
+ .orElseThrow(() -> new ResourceNotFoundException("Asset does not exist with id %s".formatted(assetId)));
+
+ if (!asset.getOwnerIdentifier().equals(connectorId)) {
+ throw new ResourceNotFoundException("Asset does not exist with id %s".formatted(assetId));
+ }
+
+ if (!asset.getFile().exists()) {
+ throw new IllegalStateException("Content does not exist for asset with id %s".formatted(assetId));
+ }
+
+ final StreamingOutput streamingOutput = outputStream -> {
+ try (final InputStream assetInputStream = new FileInputStream(asset.getFile())) {
+ assetInputStream.transferTo(outputStream);
+ }
+ };
+
+ return generateOkResponse(streamingOutput)
+ .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", asset.getName()))
+ .build();
+ }
+
// -----------------
// setters
// -----------------
@@ -1398,4 +1701,10 @@ public class ConnectorResource extends ApplicationResource {
public void setFlowResource(final FlowResource flowResource) {
this.flowResource = flowResource;
}
+
+ @Autowired(required = false)
+ public void setUploadRequestReplicator(final UploadRequestReplicator uploadRequestReplicator) {
+ this.uploadRequestReplicator = uploadRequestReplicator;
+ }
+
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
index 072c9949a1..8544e33ffa 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
@@ -535,7 +535,7 @@ public class ParameterContextResource extends AbstractParameterResource {
final Asset asset = assetManager.getAsset(assetId)
.orElseThrow(() -> new ResourceNotFoundException("Asset does not exist with the id %s".formatted(assetId)));
- if (!asset.getParameterContextIdentifier().equals(parameterContextId)) {
+ if (!asset.getOwnerIdentifier().equals(parameterContextId)) {
throw new ResourceNotFoundException("Asset does not exist with id %s".formatted(assetId));
}
@@ -810,7 +810,7 @@ public class ParameterContextResource extends AbstractParameterResource {
}
final Asset asset = assetManager.getAsset(referencedAsset.getId())
.orElseThrow(() -> new IllegalArgumentException("Request contains a reference to an Asset (%s) that does not exist".formatted(referencedAsset)));
- if (!asset.getParameterContextIdentifier().equals(parameterContextDto.getId())) {
+ if (!asset.getOwnerIdentifier().equals(parameterContextDto.getId())) {
throw new IllegalArgumentException("Request contains a reference to an Asset (%s) that does not exist in Parameter Context (%s)"
.formatted(referencedAsset, parameterContextDto.getId()));
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index e48a59325b..33680eb043 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -66,6 +66,7 @@ import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.PropertyDependency;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.ConnectorAssetRepository;
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.components.validation.ValidationState;
@@ -316,6 +317,7 @@ public final class DtoFactory {
private EntityFactory entityFactory;
private Authorizer authorizer;
private ExtensionManager extensionManager;
+ private ConnectorAssetRepository connectorAssetRepository;
public ControllerConfigurationDTO createControllerConfigurationDto(final ControllerFacade controllerFacade) {
final ControllerConfigurationDTO dto = new ControllerConfigurationDTO();
@@ -5204,14 +5206,18 @@ public final class DtoFactory {
this.entityFactory = entityFactory;
}
- public void setBulletinRepository(BulletinRepository bulletinRepository) {
+ public void setBulletinRepository(final BulletinRepository bulletinRepository) {
this.bulletinRepository = bulletinRepository;
}
- public void setExtensionManager(ExtensionManager extensionManager) {
+ public void setExtensionManager(final ExtensionManager extensionManager) {
this.extensionManager = extensionManager;
}
+ public void setConnectorAssetRepository(final ConnectorAssetRepository connectorAssetRepository) {
+ this.connectorAssetRepository = connectorAssetRepository;
+ }
+
private ProcessingPerformanceStatusDTO createProcessingPerformanceStatusDTO(final ProcessingPerformanceStatus performanceStatus) {
final ProcessingPerformanceStatusDTO performanceStatusDTO = new ProcessingPerformanceStatusDTO();
@@ -5345,7 +5351,12 @@ public final class DtoFactory {
switch (valueReference) {
case StringLiteralValue stringLiteral -> dto.setValue(stringLiteral.getValue());
- case AssetReference assetRef -> dto.setAssetIdentifier(assetRef.getAssetIdentifier());
+ case AssetReference assetRef -> {
+ final List<AssetReferenceDTO> assetReferences = assetRef.getAssetIdentifiers().stream()
+ .map(this::createConnectorAssetReferenceDto)
+ .toList();
+ dto.setAssetReferences(assetReferences);
+ }
case SecretReference secretRef -> {
dto.setSecretProviderId(secretRef.getProviderId());
dto.setSecretProviderName(secretRef.getProviderName());
@@ -5357,6 +5368,11 @@ public final class DtoFactory {
return dto;
}
+ private AssetReferenceDTO createConnectorAssetReferenceDto(final String assetId) {
+ final String assetName = connectorAssetRepository.getAsset(assetId).map(Asset::getName).orElse(assetId);
+ return new AssetReferenceDTO(assetId, assetName);
+ }
+
private ConnectorPropertyDescriptorDTO createConnectorPropertyDescriptorDto(final ConnectorPropertyDescriptor propertyDescriptor) {
if (propertyDescriptor == null) {
return null;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
index 24b4a42c6c..f51fb0c0c2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
@@ -178,6 +178,7 @@ public class WebApplicationConfiguration {
dtoFactory.setControllerServiceProvider(flowController.getControllerServiceProvider());
dtoFactory.setEntityFactory(entityFactory());
dtoFactory.setExtensionManager(extensionManager);
+ dtoFactory.setConnectorAssetRepository(flowController.getConnectorRepository().getAssetRepository());
return dtoFactory;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
index 53c87afc99..a494cf47ba 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.web.dao;
+import org.apache.nifi.asset.Asset;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ConfigVerificationResult;
@@ -23,7 +24,10 @@ import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.List;
+import java.util.Optional;
public interface ConnectorDAO {
@@ -49,11 +53,21 @@ public interface ConnectorDAO {
void applyConnectorUpdate(String id, ConnectorUpdateContext updateContext);
+ void discardWorkingConfiguration(String id);
+
void verifyCanVerifyConfigurationStep(String id, String configurationStepName);
List<ConfigVerificationResult> verifyConfigurationStep(String id, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
List<AllowableValue> fetchAllowableValues(String id, String stepName, String propertyName, String filter);
-}
+ void verifyCreateAsset(String id);
+
+ Asset createAsset(String id, String assetId, String assetName, InputStream content) throws IOException;
+
+ List<Asset> getAssets(String id);
+
+ Optional<Asset> getAsset(String assetId);
+
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
index 8dbdfa538a..d4b2183604 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
@@ -16,15 +16,20 @@
*/
package org.apache.nifi.web.dao.impl;
+import org.apache.nifi.asset.Asset;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.connector.AssetReference;
+import org.apache.nifi.components.connector.ConnectorAssetRepository;
+import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.ConnectorValueType;
+import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.components.connector.NamedStepConfiguration;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
@@ -32,20 +37,32 @@ import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.web.NiFiCoreException;
import org.apache.nifi.web.ResourceNotFoundException;
+import org.apache.nifi.web.api.dto.AssetReferenceDTO;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.web.dao.ConnectorDAO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
@Repository
public class StandardConnectorDAO implements ConnectorDAO {
+ private static final Logger logger = LoggerFactory.getLogger(StandardConnectorDAO.class);
+
private FlowController flowController;
@Autowired
@@ -61,6 +78,10 @@ public class StandardConnectorDAO implements ConnectorDAO {
return flowController.getConnectorRepository();
}
+ private ConnectorAssetRepository getConnectorAssetRepository() {
+ return flowController.getConnectorRepository().getAssetRepository();
+ }
+
@Override
public boolean hasConnector(final String id) {
return getConnectorRepository().getConnector(id) != null;
@@ -91,6 +112,7 @@ public class StandardConnectorDAO implements ConnectorDAO {
@Override
public void deleteConnector(final String id) {
getConnectorRepository().removeConnector(id);
+ getConnectorAssetRepository().deleteAssets(id);
}
@Override
@@ -153,21 +175,36 @@ public class StandardConnectorDAO implements ConnectorDAO {
final ConnectorValueType valueType = dto.getValueType() != null ? ConnectorValueType.valueOf(dto.getValueType()) : ConnectorValueType.STRING_LITERAL;
return switch (valueType) {
case STRING_LITERAL -> new StringLiteralValue(dto.getValue());
- case ASSET_REFERENCE -> new AssetReference(dto.getAssetIdentifier());
+ case ASSET_REFERENCE -> new AssetReference(convertToAssetIdentifiers(dto.getAssetReferences()));
case SECRET_REFERENCE -> new SecretReference(dto.getSecretProviderId(), dto.getSecretProviderName(), dto.getSecretName(), dto.getFullyQualifiedSecretName());
};
}
+ private Set<String> convertToAssetIdentifiers(final List<AssetReferenceDTO> assetReferenceDTOs) {
+ if (assetReferenceDTOs == null || assetReferenceDTOs.isEmpty()) {
+ return Collections.emptySet();
+ }
+ return assetReferenceDTOs.stream().map(AssetReferenceDTO::getId).collect(Collectors.toSet());
+ }
+
@Override
public void applyConnectorUpdate(final String id, final ConnectorUpdateContext updateContext) {
final ConnectorNode connector = getConnector(id);
try {
getConnectorRepository().applyUpdate(connector, updateContext);
+ cleanUpAssets(connector);
} catch (final Exception e) {
throw new NiFiCoreException("Failed to apply connector update: " + e, e);
}
}
+ @Override
+ public void discardWorkingConfiguration(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ connector.discardWorkingConfiguration();
+ cleanUpAssets(connector);
+ }
+
@Override
public void verifyCanVerifyConfigurationStep(final String id, final String configurationStepName) {
// Verify that the connector exists
@@ -190,6 +227,64 @@ public class StandardConnectorDAO implements ConnectorDAO {
return connector.fetchAllowableValues(stepName, propertyName, filter);
}
}
+
+ @Override
+ public void verifyCreateAsset(final String id) {
+ getConnector(id);
+ }
+
+ @Override
+ public Asset createAsset(final String id, final String assetId, final String assetName, final InputStream content) throws IOException {
+ final ConnectorAssetRepository assetRepository = getConnectorAssetRepository();
+ return assetRepository.storeAsset(id, assetId, assetName, content);
+ }
+
+ @Override
+ public List<Asset> getAssets(final String id) {
+ final ConnectorAssetRepository assetRepository = getConnectorAssetRepository();
+ return assetRepository.getAssets(id);
+ }
+
+ @Override
+ public Optional<Asset> getAsset(final String assetId) {
+ final ConnectorAssetRepository assetRepository = getConnectorAssetRepository();
+ return assetRepository.getAsset(assetId);
+ }
+
+ private void cleanUpAssets(final ConnectorNode connector) {
+ final FrameworkFlowContext activeFlowContext = connector.getActiveFlowContext();
+ final ConnectorConfiguration activeConfiguration = activeFlowContext.getConfigurationContext().toConnectorConfiguration();
+
+ final Set<String> referencedAssetIds = new HashSet<>();
+ for (final NamedStepConfiguration namedStepConfiguration : activeConfiguration.getNamedStepConfigurations()) {
+ final StepConfiguration stepConfiguration = namedStepConfiguration.configuration();
+ final Map<String, ConnectorValueReference> stepPropertyValues = stepConfiguration.getPropertyValues();
+ if (stepPropertyValues == null) {
+ continue;
+ }
+ for (final ConnectorValueReference valueReference : stepPropertyValues.values()) {
+ if (valueReference instanceof AssetReference assetReference) {
+ referencedAssetIds.addAll(assetReference.getAssetIdentifiers());
+ }
+ }
+ }
+
+ logger.debug("Found {} assets referenced for Connector [{}]", referencedAssetIds.size(), connector.getIdentifier());
+
+ final ConnectorAssetRepository assetRepository = getConnectorAssetRepository();
+ final List<Asset> allConnectorAssets = assetRepository.getAssets(connector.getIdentifier());
+ for (final Asset asset : allConnectorAssets) {
+ final String assetId = asset.getIdentifier();
+ if (!referencedAssetIds.contains(assetId)) {
+ try {
+ logger.info("Deleting unreferenced asset [id={},name={}] for connector [{}]", assetId, asset.getName(), connector.getIdentifier());
+ assetRepository.deleteAsset(assetId);
+ } catch (final Exception e) {
+ logger.warn("Unable to delete unreferenced asset [id={},name={}] for connector [{}]", assetId, asset.getName(), connector.getIdentifier(), e);
+ }
+ }
+ }
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
index b151f08575..016e4962d2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
@@ -350,7 +350,7 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
if (parameter != null) {
final List<Asset> assets = parameter.getReferencedAssets() == null ? Collections.emptyList() : parameter.getReferencedAssets();
for (final Asset asset : assets) {
- if (!asset.getParameterContextIdentifier().equals(parameterContextDto.getId())) {
+ if (!asset.getOwnerIdentifier().equals(parameterContextDto.getId())) {
throw new IllegalArgumentException(String.format("Parameter [%s] is not allowed to reference asset [%s] which does not belong to parameter context [%s]",
parameter.getDescriptor().getName(), asset.getName(), parameterContextDto.getId()));
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
index 19dff6703a..420c650620 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
@@ -17,6 +17,7 @@
package org.apache.nifi.web.dao.impl;
import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.connector.ConnectorAssetRepository;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorUpdateContext;
@@ -57,6 +58,9 @@ class StandardConnectorDAOTest {
@Mock
private ConnectorUpdateContext connectorUpdateContext;
+ @Mock
+ private ConnectorAssetRepository connectorAssetRepository;
+
private static final String CONNECTOR_ID = "test-connector-id";
private static final String STEP_NAME = "test-step";
private static final String PROPERTY_NAME = "test-property";
@@ -67,6 +71,7 @@ class StandardConnectorDAOTest {
connectorDAO.setFlowController(flowController);
when(flowController.getConnectorRepository()).thenReturn(connectorRepository);
+ when(connectorRepository.getAssetRepository()).thenReturn(connectorAssetRepository);
}
@Test
@@ -212,5 +217,25 @@ class StandardConnectorDAOTest {
verify(connectorRepository).getConnector(CONNECTOR_ID);
}
+
+ @Test
+ void testDeleteConnectorRemovesConnectorAndAssets() {
+ connectorDAO.deleteConnector(CONNECTOR_ID);
+
+ verify(connectorRepository).removeConnector(CONNECTOR_ID);
+ verify(connectorAssetRepository).deleteAssets(CONNECTOR_ID);
+ }
+
+ @Test
+ void testDeleteConnectorDoesNotDeleteAssetsWhenRemovalFails() {
+ doThrow(new RuntimeException("Removal failed")).when(connectorRepository).removeConnector(CONNECTOR_ID);
+
+ assertThrows(RuntimeException.class, () ->
+ connectorDAO.deleteConnector(CONNECTOR_ID)
+ );
+
+ verify(connectorRepository).removeConnector(CONNECTOR_ID);
+ verify(connectorAssetRepository, never()).deleteAssets(any());
+ }
}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/AssetConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/AssetConnector.java
new file mode 100644
index 0000000000..cbab02f64c
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/AssetConnector.java
@@ -0,0 +1,93 @@
+/*
+ * 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.connectors.tests.system;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.connector.AbstractConnector;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.PropertyType;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.flow.VersionedExternalFlow;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Example Connector used by system tests for exercising Connector Asset APIs.
+ * <p>
+ * The connector defines a single configuration step with one property whose type is ASSET.
+ * The connector itself does not manipulate the flow; it is intended only to drive API-level tests.
+ */
+public class AssetConnector extends AbstractConnector {
+
+ static final ConnectorPropertyDescriptor ASSET_PROPERTY = new ConnectorPropertyDescriptor.Builder()
+ .name("Test Asset")
+ .description("Asset used for validating Connector Asset APIs in system tests")
+ .type(PropertyType.ASSET)
+ .required(true)
+ .build();
+
+ private static final ConnectorPropertyGroup ASSET_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Asset Configuration")
+ .description("Configuration properties related to Assets")
+ .addProperty(ASSET_PROPERTY)
+ .build();
+
+ private static final ConfigurationStep ASSET_CONFIGURATION_STEP = new ConfigurationStep.Builder()
+ .name("Asset Configuration")
+ .description("Configuration Step that exposes an ASSET-type property")
+ .propertyGroups(List.of(ASSET_GROUP))
+ .build();
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ // This Connector is intended only for exercising the REST API and does not manage a flow.
+ return null;
+ }
+
+ @Override
+ public void prepareForUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
+ // No-op: this connector does not manipulate the flow.
+ }
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ return List.of(ASSET_CONFIGURATION_STEP);
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
+ // No-op: this connector does not manipulate the flow.
+ }
+
+ @Override
+ protected void onStepConfigured(final String stepName, final FlowContext workingContext) throws FlowUpdateException {
+ // No-op: there is no additional behavior when the step is configured.
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
+ // No additional verification is required for this simple test connector.
+ return List.of();
+ }
+}
+
+
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
index 8231782690..85b4fb889a 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -14,3 +14,6 @@
# limitations under the License.
org.apache.nifi.connectors.tests.system.NopConnector
+org.apache.nifi.connectors.tests.system.AssetConnector
+
+
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorAssetsIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorAssetsIT.java
new file mode 100644
index 0000000000..9016c504ce
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorAssetsIT.java
@@ -0,0 +1,142 @@
+/*
+ * 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.tests.system.connectors;
+
+import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.toolkit.client.ConnectorClient;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.util.file.FileUtils;
+import org.apache.nifi.web.api.entity.AssetEntity;
+import org.apache.nifi.web.api.entity.AssetsEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Clustered variants of the connector system tests that verify connector assets
+ * are synchronized across all cluster nodes.
+ *
+ * The structure of these tests is based on the clustered parameter context
+ * asset synchronization tests in {@code ClusteredParameterContextIT}.
+ */
+public class ClusteredConnectorAssetsIT extends ConnectorAssetsIT {
+
+ @Override
+ public NiFiInstanceFactory getInstanceFactory() {
+ return createTwoNodeInstanceFactory();
+ }
+
+ @Test
+ public void testSynchronizeConnectorAssets() throws NiFiClientException, IOException, InterruptedException {
+ waitForAllNodesConnected();
+
+ // Create a Connector instance using the AssetConnector test extension
+ final ConnectorEntity connector = getClientUtil().createConnector("AssetConnector");
+ assertNotNull(connector);
+ assertNotNull(connector.getId());
+
+ final String connectorId = connector.getId();
+
+ // Upload an Asset to the Connector
+ final File assetFile = new File("src/test/resources/sample-assets/helloworld.txt");
+ final ConnectorClient connectorClient = getNifiClient().getConnectorClient();
+ final AssetEntity assetEntity = connectorClient.createAsset(connectorId, assetFile.getName(), assetFile);
+
+ assertNotNull(assetEntity);
+ assertNotNull(assetEntity.getAsset());
+ assertNotNull(assetEntity.getAsset().getId());
+ assertEquals(assetFile.getName(), assetEntity.getAsset().getName());
+
+ final String uploadedAssetId = assetEntity.getAsset().getId();
+
+ // List the Connector's Assets and verify that the uploaded Asset is present
+ final AssetsEntity assetsEntity = connectorClient.getAssets(connectorId);
+ assertNotNull(assetsEntity);
+ assertNotNull(assetsEntity.getAssets());
+ assertFalse(assetsEntity.getAssets().isEmpty());
+
+ final boolean assetFound = assetsEntity.getAssets().stream()
+ .filter(a -> a.getAsset() != null)
+ .anyMatch(a -> uploadedAssetId.equals(a.getAsset().getId()));
+
+ assertTrue(assetFound);
+
+ // Check that the asset exists in the connector-assets directory for each node
+ final File node1Dir = getNiFiInstance().getNodeInstance(1).getInstanceDirectory();
+ final File node1AssetsDir = new File(node1Dir, "connector-assets");
+ final File node1ConnectorDir = new File(node1AssetsDir, connectorId);
+ assertTrue(node1ConnectorDir.exists());
+
+ final File node2Dir = getNiFiInstance().getNodeInstance(2).getInstanceDirectory();
+ final File node2AssetsDir = new File(node2Dir, "connector-assets");
+ final File node2ConnectorDir = new File(node2AssetsDir, connectorId);
+ assertTrue(node2ConnectorDir.exists());
+
+ final File[] node1AssetIdDirs = node1ConnectorDir.listFiles();
+ assertNotNull(node1AssetIdDirs);
+ assertEquals(1, node1AssetIdDirs.length);
+
+ final File[] node2AssetIdDirs = node2ConnectorDir.listFiles();
+ assertNotNull(node2AssetIdDirs);
+ assertEquals(1, node2AssetIdDirs.length);
+
+ // Stop node 2 and delete its connector-assets directory
+ disconnectNode(2);
+ getNiFiInstance().getNodeInstance(2).stop();
+
+ FileUtils.deleteFilesInDir(node2AssetsDir, (dir, name) -> true, null, true, true);
+ assertTrue(node2AssetsDir.delete());
+ assertFalse(node2AssetsDir.exists());
+
+ // Start node 2 again and wait for it to rejoin the cluster
+ getNiFiInstance().getNodeInstance(2).start(true);
+ reconnectNode(2);
+ waitForAllNodesConnected();
+
+ // Verify node 2 connector assets directory is recreated and contains the expected asset
+ assertTrue(node2AssetsDir.exists());
+ assertTrue(node2ConnectorDir.exists());
+
+ final File[] node2AssetIdDirsAfterRestart = node2ConnectorDir.listFiles();
+ assertNotNull(node2AssetIdDirsAfterRestart);
+ assertEquals(1, node2AssetIdDirsAfterRestart.length);
+ assertEquals(uploadedAssetId, node2AssetIdDirsAfterRestart[0].getName());
+
+ // Verify that the Asset is still returned from the Connector's Assets list
+ final AssetsEntity assetsAfterRestart = connectorClient.getAssets(connectorId);
+ assertNotNull(assetsAfterRestart);
+ assertNotNull(assetsAfterRestart.getAssets());
+
+ final boolean assetStillPresent = assetsAfterRestart.getAssets().stream()
+ .filter(a -> a.getAsset() != null)
+ .anyMatch(a -> uploadedAssetId.equals(a.getAsset().getId()));
+
+ assertTrue(assetStillPresent);
+
+ connectorClient.deleteConnector(connector);
+ }
+}
+
+
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorAssetsIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorAssetsIT.java
new file mode 100644
index 0000000000..59b46ddd82
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorAssetsIT.java
@@ -0,0 +1,202 @@
+/*
+ * 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.tests.system.connectors;
+
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.client.ConnectorClient;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.web.api.dto.AssetReferenceDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+import org.apache.nifi.web.api.entity.AssetEntity;
+import org.apache.nifi.web.api.entity.AssetsEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ConnectorAssetsIT extends NiFiSystemIT {
+
+ @Test
+ public void testCreateConnectorAndUploadAsset() throws NiFiClientException, IOException, InterruptedException {
+ // Create a Connector instance using the AssetConnector test extension
+ final ConnectorEntity connector = getClientUtil().createConnector("AssetConnector");
+ assertNotNull(connector);
+ assertNotNull(connector.getId());
+
+ final String connectorId = connector.getId();
+
+ // Upload an Asset to the Connector
+ final File assetFile = new File("src/test/resources/sample-assets/helloworld.txt");
+ final ConnectorClient connectorClient = getNifiClient().getConnectorClient();
+ final AssetEntity assetEntity = connectorClient.createAsset(connectorId, assetFile.getName(), assetFile);
+
+ assertNotNull(assetEntity);
+ assertNotNull(assetEntity.getAsset());
+ assertNotNull(assetEntity.getAsset().getId());
+ assertEquals(assetFile.getName(), assetEntity.getAsset().getName());
+
+ // List the Connector's Assets and verify that the uploaded Asset is present
+ final AssetsEntity assetsEntity = connectorClient.getAssets(connectorId);
+ assertNotNull(assetsEntity);
+ assertNotNull(assetsEntity.getAssets());
+ assertFalse(assetsEntity.getAssets().isEmpty());
+
+ final String uploadedAssetId = assetEntity.getAsset().getId();
+ final boolean assetFound = assetsEntity.getAssets().stream()
+ .filter(a -> a.getAsset() != null)
+ .anyMatch(a -> uploadedAssetId.equals(a.getAsset().getId()));
+
+ assertTrue(assetFound);
+
+ // Update the configuration step to reference the uploaded Asset in the Test Asset property
+ final String configurationStepName = "Asset Configuration";
+ final String propertyGroupName = "Asset Configuration";
+ final String propertyName = "Test Asset";
+
+ final ConfigurationStepEntity configurationStepEntity = connectorClient.getConfigurationStep(connectorId, configurationStepName);
+ assertNotNull(configurationStepEntity);
+ assertNotNull(configurationStepEntity.getConfigurationStep());
+
+ final ConfigurationStepConfigurationDTO configurationStepConfiguration = configurationStepEntity.getConfigurationStep();
+ assertNotNull(configurationStepConfiguration.getPropertyGroupConfigurations());
+ assertFalse(configurationStepConfiguration.getPropertyGroupConfigurations().isEmpty());
+
+ PropertyGroupConfigurationDTO assetConfigurationGroup = null;
+ for (final PropertyGroupConfigurationDTO group : configurationStepConfiguration.getPropertyGroupConfigurations()) {
+ if (propertyGroupName.equals(group.getPropertyGroupName())) {
+ assetConfigurationGroup = group;
+ break;
+ }
+ }
+ assertNotNull(assetConfigurationGroup);
+
+ Map<String, ConnectorValueReferenceDTO> propertyValues = assetConfigurationGroup.getPropertyValues();
+ if (propertyValues == null) {
+ propertyValues = new HashMap<>();
+ assetConfigurationGroup.setPropertyValues(propertyValues);
+ }
+
+ final ConnectorValueReferenceDTO assetValueReference = new ConnectorValueReferenceDTO();
+ assetValueReference.setValueType("ASSET_REFERENCE");
+
+ final AssetReferenceDTO assetReferenceDTO = new AssetReferenceDTO(uploadedAssetId);
+ assetValueReference.setAssetReferences(List.of(assetReferenceDTO));
+
+ propertyValues.put(propertyName, assetValueReference);
+
+ final ConfigurationStepEntity updatedConfigurationStepEntity = connectorClient.updateConfigurationStep(configurationStepEntity);
+ assertNotNull(updatedConfigurationStepEntity);
+
+ // Retrieve the Connector configuration and verify that the Test Asset property has the Asset reference set
+ final ConnectorEntity connectorWithConfiguration = connectorClient.getConnector(connectorId);
+ assertNotNull(connectorWithConfiguration);
+ assertNotNull(connectorWithConfiguration.getComponent());
+
+ final ConnectorConfigurationDTO workingConfiguration = connectorWithConfiguration.getComponent().getWorkingConfiguration();
+ assertNotNull(workingConfiguration);
+ assertNotNull(workingConfiguration.getConfigurationStepConfigurations());
+ assertFalse(workingConfiguration.getConfigurationStepConfigurations().isEmpty());
+
+ ConfigurationStepConfigurationDTO configuredStep = null;
+ for (final ConfigurationStepConfigurationDTO stepConfiguration : workingConfiguration.getConfigurationStepConfigurations()) {
+ if (configurationStepName.equals(stepConfiguration.getConfigurationStepName())) {
+ configuredStep = stepConfiguration;
+ break;
+ }
+ }
+ assertNotNull(configuredStep);
+ assertNotNull(configuredStep.getPropertyGroupConfigurations());
+ assertFalse(configuredStep.getPropertyGroupConfigurations().isEmpty());
+
+ PropertyGroupConfigurationDTO configuredAssetGroup = null;
+ for (final PropertyGroupConfigurationDTO group : configuredStep.getPropertyGroupConfigurations()) {
+ if (propertyGroupName.equals(group.getPropertyGroupName())) {
+ configuredAssetGroup = group;
+ break;
+ }
+ }
+ assertNotNull(configuredAssetGroup);
+ assertNotNull(configuredAssetGroup.getPropertyValues());
+
+ final ConnectorValueReferenceDTO configuredAssetValue = configuredAssetGroup.getPropertyValues().get(propertyName);
+ assertNotNull(configuredAssetValue);
+ assertEquals("ASSET_REFERENCE", configuredAssetValue.getValueType());
+ assertNotNull(configuredAssetValue.getAssetReferences());
+ assertFalse(configuredAssetValue.getAssetReferences().isEmpty());
+ assertEquals(uploadedAssetId, configuredAssetValue.getAssetReferences().getFirst().getId());
+
+ // Update the configuration step again to remove the Asset reference
+ final ConfigurationStepEntity configurationStepEntityWithoutAsset = connectorClient.getConfigurationStep(connectorId, configurationStepName);
+ assertNotNull(configurationStepEntityWithoutAsset);
+ assertNotNull(configurationStepEntityWithoutAsset.getConfigurationStep());
+
+ final ConfigurationStepConfigurationDTO configurationStepConfigurationWithoutAsset = configurationStepEntityWithoutAsset.getConfigurationStep();
+ assertNotNull(configurationStepConfigurationWithoutAsset.getPropertyGroupConfigurations());
+
+ PropertyGroupConfigurationDTO groupWithoutAsset = null;
+ for (final PropertyGroupConfigurationDTO group : configurationStepConfigurationWithoutAsset.getPropertyGroupConfigurations()) {
+ if (propertyGroupName.equals(group.getPropertyGroupName())) {
+ groupWithoutAsset = group;
+ break;
+ }
+ }
+ assertNotNull(groupWithoutAsset);
+
+ configuredAssetValue.setAssetReferences(null);
+ final Map<String, ConnectorValueReferenceDTO> propertyValuesWithoutAsset = groupWithoutAsset.getPropertyValues();
+ propertyValuesWithoutAsset.put(propertyName, configuredAssetValue);
+
+ final ConfigurationStepEntity configurationStepEntityAfterRemoval = connectorClient.updateConfigurationStep(configurationStepEntityWithoutAsset);
+ assertNotNull(configurationStepEntityAfterRemoval);
+
+ // Apply the connector update so that unreferenced assets are cleaned up based on the active configuration
+ final ConnectorEntity connectorBeforeApply = connectorClient.getConnector(connectorId);
+ assertNotNull(connectorBeforeApply);
+ connectorBeforeApply.setDisconnectedNodeAcknowledged(true);
+
+ final ConnectorEntity connectorAfterApply = connectorClient.applyUpdate(connectorBeforeApply);
+ assertNotNull(connectorAfterApply);
+
+ // Verify that the Asset has been removed from the Connector's Assets list
+ final AssetsEntity assetsAfterRemoval = connectorClient.getAssets(connectorId);
+ assertNotNull(assetsAfterRemoval);
+
+ final boolean assetStillPresent = assetsAfterRemoval.getAssets() != null && assetsAfterRemoval.getAssets().stream()
+ .filter(a -> a.getAsset() != null)
+ .anyMatch(a -> uploadedAssetId.equals(a.getAsset().getId()));
+
+ assertFalse(assetStillPresent);
+
+ connectorClient.deleteConnector(connectorAfterApply);
+ }
+}
+
+
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
index 504558232e..923604ba5f 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
@@ -16,6 +16,8 @@
*/
package org.apache.nifi.toolkit.client;
+import org.apache.nifi.web.api.entity.AssetEntity;
+import org.apache.nifi.web.api.entity.AssetsEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ConnectorEntity;
@@ -24,7 +26,9 @@ import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.VerifyConnectorConfigStepRequestEntity;
+import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
/**
* Client for interacting with the Connector REST endpoints.
@@ -251,6 +255,40 @@ public interface ConnectorClient {
*/
ProcessGroupStatusEntity getStatus(String connectorId, boolean recursive) throws NiFiClientException, IOException;
+ /**
+ * Creates an asset in the given connector.
+ *
+ * @param connectorId the connector ID
+ * @param assetName the asset name
+ * @param file the file
+ * @return the created asset entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ AssetEntity createAsset(String connectorId, String assetName, File file) throws NiFiClientException, IOException;
+
+ /**
+ * Lists the assets for the given connector.
+ *
+ * @param connectorId the connector ID
+ * @return the list of asset entities
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ AssetsEntity getAssets(String connectorId) throws NiFiClientException, IOException;
+
+ /**
+ * Retrieves the content of the given asset.
+ *
+ * @param connectorId the connector ID
+ * @param assetId the asset ID
+ * @param outputDirectory the directory to write the asset content
+ * @return the path to the asset content
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ Path getAssetContent(String connectorId, String assetId, File outputDirectory) throws NiFiClientException, IOException;
+
/**
* Indicates that mutable requests should indicate that the client has
* acknowledged that the node is disconnected.
@@ -258,3 +296,4 @@ public interface ConnectorClient {
void acknowledgeDisconnectedNode();
}
+
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
index 0c184aa125..8df19b09d5 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
@@ -19,11 +19,14 @@ package org.apache.nifi.toolkit.client.impl;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.toolkit.client.ConnectorClient;
import org.apache.nifi.toolkit.client.NiFiClientException;
import org.apache.nifi.toolkit.client.RequestConfig;
import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.entity.AssetEntity;
+import org.apache.nifi.web.api.entity.AssetsEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ConnectorEntity;
@@ -33,8 +36,15 @@ import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.VerifyConnectorConfigStepRequestEntity;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Objects;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
/**
* Jersey implementation of ConnectorClient.
@@ -326,6 +336,9 @@ public class JerseyConnectorClient extends AbstractJerseyClient implements Conne
if (connectorEntity == null) {
throw new IllegalArgumentException("Connector entity cannot be null");
}
+ if (StringUtils.isBlank(connectorEntity.getId())) {
+ throw new IllegalArgumentException("Connector id cannot be null or blank");
+ }
if (connectorEntity.getRevision() == null) {
throw new IllegalArgumentException("Revision cannot be null");
}
@@ -384,4 +397,77 @@ public class JerseyConnectorClient extends AbstractJerseyClient implements Conne
return getRequestBuilder(target).get(ProcessGroupStatusEntity.class);
});
}
+
+ @Override
+ public AssetEntity createAsset(final String connectorId, final String assetName, final File file) throws NiFiClientException, IOException {
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector id cannot be null or blank");
+ }
+ if (StringUtils.isBlank(assetName)) {
+ throw new IllegalArgumentException("Asset name cannot be null or blank");
+ }
+ if (file == null) {
+ throw new IllegalArgumentException("File cannot be null");
+ }
+ if (!file.exists()) {
+ throw new FileNotFoundException(file.getAbsolutePath());
+ }
+
+ try (final InputStream assetInputStream = new FileInputStream(file)) {
+ return executeAction("Error creating Connector Asset " + assetName + " for Connector " + connectorId, () -> {
+ final WebTarget target = connectorsTarget
+ .path("{id}/assets")
+ .resolveTemplate("id", connectorId);
+
+ return getRequestBuilder(target)
+ .header("Filename", assetName)
+ .post(
+ Entity.entity(assetInputStream, MediaType.APPLICATION_OCTET_STREAM_TYPE),
+ AssetEntity.class);
+ });
+ }
+ }
+
+ @Override
+ public AssetsEntity getAssets(final String connectorId) throws NiFiClientException, IOException {
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector id cannot be null or blank");
+ }
+
+ return executeAction("Error retrieving Connector assets", () -> {
+ final WebTarget target = connectorsTarget
+ .path("{id}/assets")
+ .resolveTemplate("id", connectorId);
+ return getRequestBuilder(target).get(AssetsEntity.class);
+ });
+ }
+
+ @Override
+ public Path getAssetContent(final String connectorId, final String assetId, final File outputDirectory) throws NiFiClientException, IOException {
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector id cannot be null or blank");
+ }
+ if (StringUtils.isBlank(assetId)) {
+ throw new IllegalArgumentException("Asset id cannot be null or blank");
+ }
+
+ return executeAction("Error getting Connector asset content", () -> {
+ final WebTarget target = connectorsTarget
+ .path("{id}/assets/{assetId}")
+ .resolveTemplate("id", connectorId)
+ .resolveTemplate("assetId", assetId);
+
+ final Response response = getRequestBuilder(target)
+ .accept(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+ .get();
+
+ final String filename = getContentDispositionFilename(response);
+ final File assetFile = new File(outputDirectory, filename);
+
+ try (final InputStream responseInputStream = response.readEntity(InputStream.class)) {
+ Files.copy(responseInputStream, assetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ return assetFile.toPath();
+ }
+ });
+ }
}
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyNiFiClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyNiFiClient.java
index 024b9f4a60..c99f15d9e9 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyNiFiClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyNiFiClient.java
@@ -362,7 +362,7 @@ public class JerseyNiFiClient implements NiFiClient {
JacksonJaxbJsonProvider jacksonJaxbJsonProvider = new JacksonJaxbJsonProvider();
ObjectMapper mapper = new ObjectMapper();
- mapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));
+ mapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.ALWAYS));
mapper.setAnnotationIntrospector(new JakartaXmlBindAnnotationIntrospector(mapper.getTypeFactory()));
// Ignore unknown properties so that deployed client remain compatible with
// future versions of NiFi that add new fields
(nifi) 27/40: NIFI-15445: Removed 'disabled' state from Connectors; also fixed bug … (#10749)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit e07c90b7a6f7bf28715db6a2bc25a07a2407c274
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Jan 9 17:27:48 2026 -0500
NIFI-15445: Removed 'disabled' state from Connectors; also fixed bug … (#10749)
* NIFI-15445: Removed 'disabled' state from Connectors; also fixed bug to ensure that if we fail to apply an update to a Connector that we stop it.
* NIFI-15445: Addressed review feedback
* NIFI-15445: Fixed checkstyle
---
.../org/apache/nifi/web/api/dto/ConnectorDTO.java | 4 +-
.../flow/mapping/VersionedComponentFlowMapper.java | 5 +-
.../nifi/components/connector/ConnectorNode.java | 9 ---
.../nifi/components/connector/ConnectorState.java | 1 -
.../apache/nifi/controller/ProcessScheduler.java | 4 --
.../connector/StandardConnectorNode.java | 78 +++++++---------------
.../scheduling/StandardProcessScheduler.java | 27 --------
.../connector/StandardConnectorNodeIT.java | 3 +-
.../connector/TestStandardConnectorNode.java | 68 +------------------
.../org/apache/nifi/audit/ConnectorAuditor.java | 52 ---------------
.../apache/nifi/web/StandardNiFiServiceFacade.java | 1 -
.../java/org/apache/nifi/web/dao/ConnectorDAO.java | 4 --
.../nifi/web/dao/impl/StandardConnectorDAO.java | 12 ----
.../scheduling/StatelessProcessScheduler.java | 10 ---
14 files changed, 32 insertions(+), 246 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
index a2253fdfeb..d2d08590a3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
@@ -29,7 +29,7 @@ public class ConnectorDTO extends ComponentDTO {
private String name;
private String type;
private BundleDTO bundle;
- private String state; // RUNNING, STOPPED, DISABLED
+ private String state; // RUNNING, STOPPED
private String managedProcessGroupId;
private ConnectorConfigurationDTO activeConfiguration;
private ConnectorConfigurationDTO workingConfiguration;
@@ -69,7 +69,7 @@ public class ConnectorDTO extends ComponentDTO {
this.bundle = bundle;
}
- @Schema(description = "The state of the Connector.", allowableValues = {"RUNNING", "STOPPED", "DISABLED"})
+ @Schema(description = "The state of the Connector.", allowableValues = {"RUNNING", "STOPPED"})
public String getState() {
return state;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
index 50a896ebf5..dc9ac31f85 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
@@ -21,17 +21,17 @@ import org.apache.commons.lang3.ClassUtils;
import org.apache.nifi.asset.Asset;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.listen.ListenPortDefinition;
import org.apache.nifi.components.connector.AssetReference;
-import org.apache.nifi.components.connector.NamedStepConfiguration;
import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.components.connector.NamedStepConfiguration;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
+import org.apache.nifi.components.listen.ListenPortDefinition;
import org.apache.nifi.components.resource.ResourceCardinality;
import org.apache.nifi.components.resource.ResourceDefinition;
import org.apache.nifi.connectable.Connectable;
@@ -1106,7 +1106,6 @@ public class VersionedComponentFlowMapper {
}
return switch (connectorState) {
- case DISABLED -> org.apache.nifi.flow.ScheduledState.DISABLED;
case RUNNING, STARTING -> org.apache.nifi.flow.ScheduledState.RUNNING;
default -> org.apache.nifi.flow.ScheduledState.ENABLED;
};
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index d4121b2db9..146be70d73 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -161,15 +161,6 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
Collection<ValidationResult> getValidationErrors();
- /**
- * Enables the Connector. This method should only be invoked via the ConnectorRepository.
- */
- void enable();
-
- /**
- * Disables the Connector. This method should only be invoked via the ConnectorRepository.
- */
- void disable();
ValidationState getValidationState();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorState.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorState.java
index ad30ab832b..c13999c4fa 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorState.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorState.java
@@ -22,7 +22,6 @@ public enum ConnectorState {
RUNNING,
STOPPING,
STOPPED,
- DISABLED,
DRAINING,
PURGING,
PREPARING_FOR_UPDATE,
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
index fba71ebca8..94ca3da1b1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
@@ -269,10 +269,6 @@ public interface ProcessScheduler {
CompletableFuture<Void> stopConnector(ConnectorNode connectorNode);
- void enableConnector(ConnectorNode connectorNode);
-
- void disableConnector(ConnectorNode connectorNode);
-
void onConnectorRemoved(ConnectorNode connectorNode);
/**
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 671e88efea..5edd2d3fb1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -37,6 +37,7 @@ import org.apache.nifi.connectable.FlowFileActivity;
import org.apache.nifi.connectable.FlowFileTransferCounts;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.controller.queue.DropFlowFileStatus;
+import org.apache.nifi.controller.queue.QueueSize;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.VersionedConfigurationStep;
@@ -227,7 +228,19 @@ public class StandardConnectorNode implements ConnectorNode {
@Override
public void applyUpdate() throws FlowUpdateException {
- applyUpdate(workingFlowContext);
+ try {
+ applyUpdate(workingFlowContext);
+ } catch (final FlowUpdateException e) {
+ // Since we failed to update, make sure that we stop the Connector. Note that we do not do this for all
+ // throwables because IllegalStateException for example indicates that we did not even attempt to perform the update.
+ try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connectorDetails.getConnector().getClass(), getIdentifier())) {
+ connectorDetails.getConnector().stop(activeFlowContext);
+ } catch (final Throwable stopThrowable) {
+ e.addSuppressed(stopThrowable);
+ }
+
+ throw e;
+ }
}
private void applyUpdate(final FrameworkFlowContext contextToInherit) throws FlowUpdateException {
@@ -321,39 +334,6 @@ public class StandardConnectorNode implements ConnectorNode {
return stateTransition.getDesiredState();
}
- @Override
- public void enable() {
- if (getCurrentState() == ConnectorState.STOPPED) {
- return;
- }
- if (getCurrentState() != ConnectorState.DISABLED) {
- throw new IllegalStateException("Cannot enable " + this + " because its desired state is currently " + getCurrentState()
- + "; it must be DISABLED in order to be enabled.");
- }
-
- stateTransition.setDesiredState(ConnectorState.STOPPED);
- if (stateTransition.trySetCurrentState(ConnectorState.DISABLED, ConnectorState.STOPPED)) {
- logger.info("Transitioned current state for {} to {}", this, ConnectorState.STOPPED);
- return;
- }
-
- logger.info("{} enabled but not currently DISABLED so set desired state to STOPPED; current state is {}", this, stateTransition.getCurrentState());
- }
-
- @Override
- public void disable() {
- stateTransition.setDesiredState(ConnectorState.DISABLED);
-
- final ConnectorState currentState = getCurrentState();
- if (currentState == ConnectorState.DISABLED || currentState == ConnectorState.STOPPED || currentState == ConnectorState.UPDATE_FAILED) {
- if (stateTransition.trySetCurrentState(currentState, ConnectorState.DISABLED)) {
- logger.info("Transitioned current state for {} to {}", this, ConnectorState.DISABLED);
- return;
- }
- }
-
- logger.info("{} disabled but not in a state that can immediately transition to DISABLED so set desired state to DISABLED; current state is {}", this, currentState);
- }
@Override
public Optional<Duration> getIdleDuration() {
@@ -424,7 +404,7 @@ public class StandardConnectorNode implements ConnectorNode {
boolean stateUpdated = false;
while (!stateUpdated) {
final ConnectorState currentState = getCurrentState();
- if (currentState == ConnectorState.STOPPED || currentState == ConnectorState.DISABLED) {
+ if (currentState == ConnectorState.STOPPED) {
logger.debug("{} is already {}; will not attempt to stop", this, currentState);
stopCompleteFuture.complete(null);
return stopCompleteFuture;
@@ -504,18 +484,9 @@ public class StandardConnectorNode implements ConnectorNode {
stopCompleteFuture.complete(null);
final ConnectorState desiredState = getDesiredState();
- switch (desiredState) {
- case DISABLED -> {
- logger.info("{} was requested to be DISABLED while it was stopping so will now transition to DISABLED", this);
- disable();
- }
- case RUNNING -> {
- logger.info("{} was requested to be RUNNING while it was stopping so will attempt to start again", this);
- start(scheduler, new CompletableFuture<>());
- }
- default -> {
- // No action needed for other states
- }
+ if (desiredState == ConnectorState.RUNNING) {
+ logger.info("{} was requested to be RUNNING while it was stopping so will attempt to start again", this);
+ start(scheduler, new CompletableFuture<>());
}
}
@@ -541,8 +512,14 @@ public class StandardConnectorNode implements ConnectorNode {
@Override
public void verifyCanDelete() {
+ final QueueSize queueSize = getActiveFlowContext().getManagedProcessGroup().getQueueSize();
+ if (queueSize.getObjectCount() > 0) {
+ throw new IllegalStateException("Cannot delete " + this + " because its Process Group has " + queueSize.getObjectCount()
+ + " FlowFiles queued; all FlowFiles must be removed before it can be deleted.");
+ }
+
final ConnectorState currentState = getCurrentState();
- if (currentState == ConnectorState.STOPPED || currentState == ConnectorState.DISABLED) {
+ if (currentState == ConnectorState.STOPPED || currentState == ConnectorState.UPDATE_FAILED || currentState == ConnectorState.UPDATED) {
return;
}
@@ -551,11 +528,6 @@ public class StandardConnectorNode implements ConnectorNode {
@Override
public void verifyCanStart() {
- final ConnectorState currentState = getCurrentState();
- if (currentState == ConnectorState.DISABLED) {
- throw new IllegalStateException("Cannot start " + this + " because its state is currently " + currentState + "; it must be fully stopped before it can be started.");
- }
-
final ValidationState state = performValidation();
if (state.getStatus() != ValidationStatus.VALID) {
throw new IllegalStateException("Cannot start " + this + " because it is not valid: " + state.getValidationErrors());
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
index eb9a0e2101..1b0f89a0db 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
@@ -23,7 +23,6 @@ import org.apache.nifi.annotation.lifecycle.OnUnscheduled;
import org.apache.nifi.annotation.notification.PrimaryNodeState;
import org.apache.nifi.authorization.resource.ComponentAuthorizable;
import org.apache.nifi.components.connector.ConnectorNode;
-import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.components.state.StateManager;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.components.validation.ValidationStatus;
@@ -924,32 +923,6 @@ public final class StandardProcessScheduler implements ProcessScheduler {
throw new UnsupportedOperationException();
}
- @Override
- public void enableConnector(final ConnectorNode connectorNode) {
- final ConnectorState currentState = connectorNode.getCurrentState();
- if (currentState != ConnectorState.DISABLED) {
- throw new IllegalStateException("Connector cannot be enabled because its state is set to " + currentState
- + " but transition to STOPPED state is allowed only from the DISABLED state");
- }
-
- connectorNode.enable();
- }
-
- @Override
- public void disableConnector(final ConnectorNode connectorNode) {
- final ConnectorState currentState = connectorNode.getCurrentState();
- if (currentState == ConnectorState.DISABLED) {
- return;
- }
-
- if (currentState != ConnectorState.STOPPED) {
- throw new IllegalStateException("Connector cannot be disabled because its state is set to " + currentState
- + " but transition to DISABLED state is allowed only from the STOPPED state");
- }
-
- connectorNode.disable();
- }
-
@Override
public void onConnectorRemoved(final ConnectorNode connectorNode) {
lifecycleStateManager.removeLifecycleState(connectorNode.getIdentifier());
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
index d2f8ce2dfc..f8e63e2723 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
@@ -481,12 +481,11 @@ public class StandardConnectorNodeIT {
}
@Test
- public void testPurgeFlowFilesRequiresStoppedState() throws FlowUpdateException {
+ public void testPurgeFlowFilesRequiresStoppedState() {
final ConnectorNode connectorNode = initializeDynamicFlowConnector();
final ProcessGroup rootGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
queueDataBySource(rootGroup, "Create FlowFile");
- connectorNode.enable();
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.start(componentLifecycleThreadPool);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index bf26c1f7ed..4eba662d26 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -25,6 +25,7 @@ import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.components.connector.components.FlowContextType;
import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.controller.queue.QueueSize;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.VersionedExternalFlow;
@@ -81,6 +82,7 @@ public class TestStandardConnectorNode {
scheduler = new FlowEngine(1, "flow-engine");
when(managedProcessGroup.purge()).thenReturn(CompletableFuture.completedFuture(null));
+ when(managedProcessGroup.getQueueSize()).thenReturn(new QueueSize(0, 0L));
flowContextFactory = new FlowContextFactory() {
@Override
@@ -143,47 +145,6 @@ public class TestStandardConnectorNode {
assertFalse(stopFuture.isCancelled());
}
- @Test
- public void testCannotStartFromDisabledState() throws FlowUpdateException {
- final StandardConnectorNode connectorNode = createConnectorNode();
- connectorNode.disable();
- assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
-
- assertThrows(IllegalStateException.class, () -> connectorNode.start(scheduler));
- }
-
- @Test
- public void testCannotTransitionFromDisabledToRunning() throws FlowUpdateException {
- final StandardConnectorNode connectorNode = createConnectorNode();
- connectorNode.disable();
- assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
-
- assertThrows(IllegalStateException.class, () -> connectorNode.start(scheduler));
-
- assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
- }
-
- @Test
- public void testEnableFromDisabledState() throws FlowUpdateException {
- final StandardConnectorNode connectorNode = createConnectorNode();
- connectorNode.disable();
- assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
-
- connectorNode.enable();
- assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
- assertEquals(ConnectorState.STOPPED, connectorNode.getDesiredState());
- }
-
- @Test
- public void testDisableFromStoppedState() throws FlowUpdateException {
- final StandardConnectorNode connectorNode = createConnectorNode();
- assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
-
- connectorNode.disable();
- assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
- assertEquals(ConnectorState.DISABLED, connectorNode.getDesiredState());
- }
-
@Test
public void testStartFutureCompletedOnlyWhenRunning() throws Exception {
final StandardConnectorNode connectorNode = createConnectorNode();
@@ -243,14 +204,6 @@ public class TestStandardConnectorNode {
connectorNode.verifyCanDelete();
}
- @Test
- public void testVerifyCanDeleteWhenDisabled() throws FlowUpdateException {
- final StandardConnectorNode connectorNode = createConnectorNode();
- connectorNode.disable();
- assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
- connectorNode.verifyCanDelete();
- }
-
@Test
public void testCannotDeleteWhenRunning() throws Exception {
final StandardConnectorNode connectorNode = createConnectorNode();
@@ -355,23 +308,6 @@ public class TestStandardConnectorNode {
assertEquals(newConfiguration, connectorNode.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration());
}
- @Test
- public void testSetConfigurationWhenDisabled() throws FlowUpdateException {
- final StandardConnectorNode connectorNode = createConnectorNode();
- connectorNode.disable();
- assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
- assertEquals(ConnectorState.DISABLED, connectorNode.getDesiredState());
-
- final ConnectorConfiguration newConfiguration = createTestConfiguration();
-
- connectorNode.transitionStateForUpdating();
- connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("testGroup", createStepConfiguration());
- connectorNode.applyUpdate();
-
- assertEquals(newConfiguration, connectorNode.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration());
- }
-
@Test
public void testSetConfigurationWithPropertyChanges() throws FlowUpdateException, ExecutionException, InterruptedException, TimeoutException {
final StandardConnectorNode connectorNode = createConnectorNode();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
index 73922f984a..57c762451f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
@@ -157,58 +157,6 @@ public class ConnectorAuditor extends NiFiAuditor {
}
}
- /**
- * Audits the enabling of a connector via enableConnector().
- *
- * @param proceedingJoinPoint join point
- * @param connectorId connector id
- * @param connectorDAO connector dao
- * @throws Throwable if an error occurs
- */
- @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
- + "execution(void enableConnector(java.lang.String)) && "
- + "args(connectorId) && "
- + "target(connectorDAO)")
- public void enableConnectorAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final ConnectorDAO connectorDAO) throws Throwable {
- final ConnectorNode connector = connectorDAO.getConnector(connectorId);
- final ConnectorState previousState = connector.getCurrentState();
-
- proceedingJoinPoint.proceed();
-
- if (isAuditable() && previousState == ConnectorState.DISABLED) {
- final Action action = generateAuditRecord(connector, Operation.Enable);
- if (action != null) {
- saveAction(action, logger);
- }
- }
- }
-
- /**
- * Audits the disabling of a connector via disableConnector().
- *
- * @param proceedingJoinPoint join point
- * @param connectorId connector id
- * @param connectorDAO connector dao
- * @throws Throwable if an error occurs
- */
- @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
- + "execution(void disableConnector(java.lang.String)) && "
- + "args(connectorId) && "
- + "target(connectorDAO)")
- public void disableConnectorAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final ConnectorDAO connectorDAO) throws Throwable {
- final ConnectorNode connector = connectorDAO.getConnector(connectorId);
- final ConnectorState previousState = connector.getCurrentState();
-
- proceedingJoinPoint.proceed();
-
- if (isAuditable() && previousState != ConnectorState.DISABLED) {
- final Action action = generateAuditRecord(connector, Operation.Disable);
- if (action != null) {
- saveAction(action, logger);
- }
- }
- }
-
/**
* Audits configuration step updates via updateConnectorConfigurationStep().
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index c540467b38..195aca6e21 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -3591,7 +3591,6 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
switch (state) {
case RUNNING -> connectorDAO.startConnector(id);
case STOPPED -> connectorDAO.stopConnector(id);
- case DISABLED -> connectorDAO.disableConnector(id);
default -> throw new IllegalArgumentException("Unsupported scheduled state for Connector: " + state);
}
controllerFacade.save();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
index a494cf47ba..a987dde126 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
@@ -45,10 +45,6 @@ public interface ConnectorDAO {
void stopConnector(String id);
- void enableConnector(String id);
-
- void disableConnector(String id);
-
void updateConnectorConfigurationStep(String id, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
void applyConnectorUpdate(String id, ConnectorUpdateContext updateContext);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
index d4b2183604..e55e6ceee0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
@@ -127,18 +127,6 @@ public class StandardConnectorDAO implements ConnectorDAO {
getConnectorRepository().stopConnector(connector);
}
- @Override
- public void enableConnector(final String id) {
- final ConnectorNode connector = getConnector(id);
- connector.enable();
- }
-
- @Override
- public void disableConnector(final String id) {
- final ConnectorNode connector = getConnector(id);
- connector.disable();
- }
-
@Override
public void updateConnectorConfigurationStep(final String id, final String configurationStepName, final ConfigurationStepConfigurationDTO configurationStepDto) {
final ConnectorNode connector = getConnector(id);
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/controller/scheduling/StatelessProcessScheduler.java b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/controller/scheduling/StatelessProcessScheduler.java
index 531b9da34b..72c026083b 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/controller/scheduling/StatelessProcessScheduler.java
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/controller/scheduling/StatelessProcessScheduler.java
@@ -345,16 +345,6 @@ public class StatelessProcessScheduler implements ProcessScheduler {
throw new UnsupportedOperationException();
}
- @Override
- public void enableConnector(final ConnectorNode connectorNode) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void disableConnector(final ConnectorNode connectorNode) {
- throw new UnsupportedOperationException();
- }
-
@Override
public void onConnectorRemoved(final ConnectorNode connectorNode) {
}
(nifi) 19/40: NIFI-15370: Add Connector ID to process groups and do not register create process groups if they have a connector ID. (#10673)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 5b8af8d4090cb8a25dc97b6f2064c174bf1188dd
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Dec 19 15:19:57 2025 -0500
NIFI-15370: Add Connector ID to process groups and do not register create process groups if they have a connector ID. (#10673)
---
.../StandardVersionedComponentSynchronizer.java | 6 ++++--
.../java/org/apache/nifi/groups/StandardProcessGroup.java | 9 ++++++++-
.../org/apache/nifi/groups/StandardProcessGroupTest.java | 3 ++-
.../java/org/apache/nifi/controller/flow/FlowManager.java | 6 +++++-
.../src/main/java/org/apache/nifi/groups/ProcessGroup.java | 5 +++++
.../apache/nifi/controller/flow/StandardFlowManager.java | 13 +++++--------
.../nifi/controller/service/mock/MockProcessGroup.java | 5 +++++
.../apache/nifi/stateless/engine/StatelessFlowManager.java | 5 +++--
8 files changed, 37 insertions(+), 15 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
index 3466d317e4..3050a7db70 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
@@ -1377,7 +1377,8 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
final Map<String, VersionedParameterContext> versionedParameterContexts,
final Map<String, ParameterProviderReference> parameterProviderReferences, ProcessGroup topLevelGroup) throws ProcessorInstantiationException {
final String id = componentIdGenerator.generateUuid(proposed.getIdentifier(), proposed.getInstanceIdentifier(), destination.getIdentifier());
- final ProcessGroup group = context.getFlowManager().createProcessGroup(id);
+ final String connectorId = destination.getConnectorIdentifier().orElse(null);
+ final ProcessGroup group = context.getFlowManager().createProcessGroup(id, connectorId);
group.setVersionedComponentId(proposed.getIdentifier());
group.setParent(destination);
group.setName(proposed.getName());
@@ -1949,7 +1950,8 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
final ProcessGroup groupToUpdate;
if (processGroup == null) {
final String groupId = synchronizationOptions.getComponentIdGenerator().generateUuid(proposed.getIdentifier(), proposed.getInstanceIdentifier(), parentGroup.getIdentifier());
- final ProcessGroup group = context.getFlowManager().createProcessGroup(groupId);
+ final String connectorId = parentGroup.getConnectorIdentifier().orElse(null);
+ final ProcessGroup group = context.getFlowManager().createProcessGroup(groupId, connectorId);
group.setVersionedComponentId(proposed.getIdentifier());
group.setParent(parentGroup);
group.setName(proposed.getName());
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
index 658ee0cc9d..9918eda3a1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
@@ -173,6 +173,7 @@ public final class StandardProcessGroup implements ProcessGroup {
private final AtomicReference<String> versionedComponentId = new AtomicReference<>();
private final AtomicReference<StandardVersionControlInformation> versionControlInfo = new AtomicReference<>();
private static final SecureRandom randomGenerator = new SecureRandom();
+ private final String connectorId;
private final ProcessScheduler scheduler;
private final ControllerServiceProvider controllerServiceProvider;
@@ -231,7 +232,7 @@ public final class StandardProcessGroup implements ProcessGroup {
final StateManagerProvider stateManagerProvider, final FlowManager flowManager,
final ReloadComponent reloadComponent, final NodeTypeProvider nodeTypeProvider,
final NiFiProperties nifiProperties, final StatelessGroupNodeFactory statelessGroupNodeFactory,
- final AssetManager assetManager) {
+ final AssetManager assetManager, final String connectorId) {
this.id = id;
this.controllerServiceProvider = serviceProvider;
@@ -245,6 +246,7 @@ public final class StandardProcessGroup implements ProcessGroup {
this.reloadComponent = reloadComponent;
this.nodeTypeProvider = nodeTypeProvider;
this.assetManager = assetManager;
+ this.connectorId = connectorId;
name = new AtomicReference<>();
position = new AtomicReference<>(new Position(0D, 0D));
@@ -338,6 +340,11 @@ public final class StandardProcessGroup implements ProcessGroup {
setLoggingAttributes();
}
+ @Override
+ public Optional<String> getConnectorIdentifier() {
+ return Optional.ofNullable(connectorId);
+ }
+
@Override
public void setPosition(final Position position) {
this.position.set(position);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/groups/StandardProcessGroupTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/groups/StandardProcessGroupTest.java
index b3f888a339..0887de6c90 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/groups/StandardProcessGroupTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/groups/StandardProcessGroupTest.java
@@ -130,7 +130,8 @@ class StandardProcessGroupTest {
nodeTypeProvider,
properties,
statelessGroupNodeFactory,
- assetManager
+ assetManager,
+ null
);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
index 266e9888d2..d756e8531e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
@@ -252,7 +252,11 @@ public interface FlowManager extends ParameterProviderLookup {
Port createLocalOutputPort(String id, String name);
- ProcessGroup createProcessGroup(String id);
+ default ProcessGroup createProcessGroup(String id) {
+ return createProcessGroup(id, null);
+ }
+
+ ProcessGroup createProcessGroup(String id, String connectorId);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
index 821f0a533d..66d66f96da 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java
@@ -138,6 +138,11 @@ public interface ProcessGroup extends ComponentAuthorizable, Positionable, Versi
*/
void setName(String name);
+ /**
+ * @return the ID of the Connector that is responsible for this Process Group, or an empty optional if no Connector is associated
+ */
+ Optional<String> getConnectorIdentifier();
+
/**
* @return the user-set comments about this ProcessGroup, or
* <code>null</code> if no comments have been set
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 7cf4bc8707..65e8011b90 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -298,19 +298,16 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
}
@Override
- public ProcessGroup createProcessGroup(final String id) {
- return createProcessGroup(id, true);
- }
-
- private ProcessGroup createProcessGroup(final String id, final boolean registerGroup) {
+ public ProcessGroup createProcessGroup(final String id, final String connectorId) {
final StatelessGroupNodeFactory statelessGroupNodeFactory = new StandardStatelessGroupNodeFactory(flowController, sslContext, flowController.createKerberosConfig(nifiProperties));
final ProcessGroup group = new StandardProcessGroup(requireNonNull(id), flowController.getControllerServiceProvider(), processScheduler, flowController.getEncryptor(),
flowController.getExtensionManager(), flowController.getStateManagerProvider(), this,
flowController.getReloadComponent(), flowController, nifiProperties, statelessGroupNodeFactory,
- flowController.getAssetManager());
+ flowController.getAssetManager(), connectorId);
- if (registerGroup) {
+ // We don't want to register the group if it's being created as part of a Connector
+ if (connectorId == null) {
onProcessGroupAdded(group);
}
@@ -767,7 +764,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
final StandardConnectorConfigurationContext activeConfigurationContext = new StandardConnectorConfigurationContext(
flowController.getConnectorAssetManager(), flowController.getConnectorRepository().getSecretsManager());
- final ProcessGroupFactory processGroupFactory = groupId -> createProcessGroup(groupId, false);
+ final ProcessGroupFactory processGroupFactory = groupId -> createProcessGroup(groupId, id);
final FlowContextFactory flowContextFactory = new FlowControllerFlowContextFactory(flowController, managedRootGroup, activeConfigurationContext, processGroupFactory);
final ConnectorNode connectorNode = new ExtensionBuilder()
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java
index decb1b90a7..8dcba71418 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java
@@ -127,6 +127,11 @@ public class MockProcessGroup implements ProcessGroup {
}
+ @Override
+ public Optional<String> getConnectorIdentifier() {
+ return Optional.empty();
+ }
+
@Override
public void setPosition(final Position position) {
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessFlowManager.java b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessFlowManager.java
index 4aed3f8ce5..a637745bb6 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessFlowManager.java
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StatelessFlowManager.java
@@ -226,7 +226,7 @@ public class StatelessFlowManager extends AbstractFlowManager implements FlowMan
}
@Override
- public ProcessGroup createProcessGroup(final String id) {
+ public ProcessGroup createProcessGroup(final String id, final String connectorId) {
final ProcessGroup created = new StandardProcessGroup(id, statelessEngine.getControllerServiceProvider(),
statelessEngine.getProcessScheduler(),
statelessEngine.getPropertyEncryptor(),
@@ -237,7 +237,8 @@ public class StatelessFlowManager extends AbstractFlowManager implements FlowMan
new StatelessNodeTypeProvider(),
null,
group -> null,
- statelessEngine.getAssetManager());
+ statelessEngine.getAssetManager(),
+ connectorId);
onProcessGroupAdded(created);
return created;
(nifi) 30/40: NIFI-15468: Ensure that we properly initialize ConnectorNode even if a Ghost Connector is created (#10775)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 8414bc7fcd1c1d951da0ac1d17088cba55fa9202
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Thu Jan 15 09:27:30 2026 -0500
NIFI-15468: Ensure that we properly initialize ConnectorNode even if a Ghost Connector is created (#10775)
This closes #10775
---
.../main/java/org/apache/nifi/controller/ExtensionBuilder.java | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index 54ed942e70..6d985e297d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -554,7 +554,7 @@ public class ExtensionBuilder {
// If an instance class loader has been created for this connector, remove it because it's no longer necessary.
extensionManager.removeInstanceClassLoader(identifier);
- return new StandardConnectorNode(
+ final ConnectorNode connectorNode = new StandardConnectorNode(
identifier,
flowController.getFlowManager(),
extensionManager,
@@ -568,7 +568,13 @@ public class ExtensionBuilder {
connectorValidationTrigger,
true
);
- }
+
+ // Initialize the ghost connector so that it can be properly configured during flow synchronization
+ final FrameworkConnectorInitializationContext initContext = createConnectorInitializationContext(managedProcessGroup, componentLog);
+ connectorNode.initializeConnector(initContext);
+
+ return connectorNode;
+ }
private void initializeDefaultValues(final Connector connector, final FrameworkFlowContext flowContext) {
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connector.getClass(), identifier)) {
(nifi) 13/40: NIFI-15352: Added ConnectorClient to toolkit-cli so that it can be us… (#10654)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit c5f53200e4b5b9b596b2f1eed05e3d28dbb8d2ac
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Wed Dec 17 11:20:56 2025 -0500
NIFI-15352: Added ConnectorClient to toolkit-cli so that it can be us… (#10654)
* NIFI-15352: Added ConnectorClient to toolkit-cli so that it can be used in system tests; created system tests for Connectors; bug fixes found running tests; updated Secret/SecretReference to support fully qualified names
* NIFI-15352: Addressed review feedback
---
.cursorrules | 3 +
.../server/StandardConnectorMockServer.java | 3 +-
.../secrets/ConnectorTestRunnerSecretProvider.java | 4 +
.../secrets/ConnectorTestRunnerSecretsManager.java | 2 +-
.../web/api/dto/ConnectorValueReferenceDTO.java | 10 +
.../ClusteredConnectorRequestReplicator.java | 38 +-
.../cluster/manager/ConnectorEntityMerger.java | 49 ++-
.../ConnectorRequestReplicatorConfiguration.java | 8 +-
.../StandardConnectorValidationTrigger.java | 56 +++
.../secrets/ParameterProviderSecretProvider.java | 2 +
.../secrets/ParameterProviderSecretsManager.java | 8 +-
.../connector/secrets/StandardSecret.java | 41 ++-
.../flow/mapping/VersionedComponentFlowMapper.java | 7 +-
.../TestParameterProviderSecretsManager.java | 29 +-
.../nifi/components/connector/ConnectorNode.java | 26 +-
.../components/connector/ConnectorRepository.java | 5 +-
.../connector/ConnectorUpdateContext.java | 26 ++
.../connector/ConnectorValidationTrigger.java | 39 +++
.../StandardConnectorConfigurationContext.java | 2 +-
.../connector/StandardConnectorNode.java | 221 ++++++++++--
.../connector/StandardConnectorRepository.java | 61 +++-
.../apache/nifi/controller/ExtensionBuilder.java | 47 ++-
.../org/apache/nifi/controller/FlowController.java | 8 +
.../nifi/controller/flow/StandardFlowManager.java | 1 +
.../serialization/VersionedFlowSynchronizer.java | 17 +-
.../connector/StandardConnectorNodeIT.java | 12 +-
.../connector/TestStandardConnectorNode.java | 67 +++-
.../apache/nifi/web/StandardNiFiServiceFacade.java | 5 +-
.../org/apache/nifi/web/api/dto/DtoFactory.java | 5 +
.../nifi/web/controller/ControllerFacade.java | 4 +
.../java/org/apache/nifi/web/dao/ConnectorDAO.java | 3 +-
.../nifi/web/dao/impl/StandardConnectorDAO.java | 7 +-
.../web/dao/impl/StandardConnectorDAOTest.java | 30 +-
.../nifi/connectors/tests/system/NopConnector.java | 120 +++++++
.../tests/system/PropertiesParameterProvider.java | 2 +-
.../org.apache.nifi.components.connector.Connector | 16 +
.../apache/nifi/tests/system/NiFiClientUtil.java | 214 ++++++++++++
.../org/apache/nifi/tests/system/NiFiSystemIT.java | 1 +
.../system/connectors/ClusteredConnectorIT.java | 128 +++++++
.../tests/system/connectors/ConnectorCrudIT.java | 146 ++++++++
.../toolkit/cli/impl/client/NiFiClientFactory.java | 11 +
.../nifi/toolkit/client/ConnectorClient.java | 260 ++++++++++++++
.../org/apache/nifi/toolkit/client/NiFiClient.java | 8 +-
.../toolkit/client/impl/JerseyConnectorClient.java | 387 +++++++++++++++++++++
.../nifi/toolkit/client/impl/JerseyNiFiClient.java | 11 +
45 files changed, 1993 insertions(+), 157 deletions(-)
diff --git a/.cursorrules b/.cursorrules
index 3bcdd1f6cd..1990f9de80 100644
--- a/.cursorrules
+++ b/.cursorrules
@@ -12,6 +12,9 @@ When it is necessary to lookup the API for reference, the API can generally be f
Building
--------
NiFi is a complex Maven codebase. Never build code (testing or otherwise) using javac. Always use `mvn` instead.
+Additionally, building a maven module using the also-make flag (`-am`) is often very expensive and slow.
+Instead, only build the specific module you are modifying. Assume that the user has already built the entire
+codebase and that only the specific module you are modifying needs to be built again.
Code Style
----------
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
index 0e5a2d86ab..38a61ae9bd 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
@@ -197,6 +197,7 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
public void applyUpdate() throws FlowUpdateException {
final ConnectorState initialDesiredState = connectorNode.getDesiredState();
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.applyUpdate();
@@ -261,7 +262,7 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
@Override
public SecretReference createSecretReference(final String secretName) {
- return new SecretReference(ConnectorTestRunner.SECRET_PROVIDER_ID, ConnectorTestRunner.SECRET_PROVIDER_NAME, secretName);
+ return new SecretReference(ConnectorTestRunner.SECRET_PROVIDER_ID, ConnectorTestRunner.SECRET_PROVIDER_NAME, secretName, secretName);
}
@Override
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java
index 3323eb9eb9..e12a3711bc 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java
@@ -53,11 +53,13 @@ public class ConnectorTestRunnerSecretProvider implements SecretProvider {
final List<Secret> secrets = new ArrayList<>();
for (final Map.Entry<String, String> entry : this.secrets.entrySet()) {
final Secret secret = new StandardSecret.Builder()
+ .providerId(ConnectorTestRunner.SECRET_PROVIDER_ID)
.providerName(ConnectorTestRunner.SECRET_PROVIDER_NAME)
.groupName(GROUP_NAME)
.name(entry.getKey())
.value(entry.getValue())
.authorizable(AUTHORIZABLE)
+ .fullyQualifiedName(GROUP_NAME + "." + entry.getKey())
.build();
secrets.add(secret);
@@ -75,9 +77,11 @@ public class ConnectorTestRunnerSecretProvider implements SecretProvider {
if (value != null) {
final Secret secret = new StandardSecret.Builder()
+ .providerId(ConnectorTestRunner.SECRET_PROVIDER_ID)
.providerName(ConnectorTestRunner.SECRET_PROVIDER_NAME)
.groupName(GROUP_NAME)
.name(secretName)
+ .fullyQualifiedName(GROUP_NAME + "." + secretName)
.value(value)
.authorizable(AUTHORIZABLE)
.build();
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretsManager.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretsManager.java
index 8af0f6a8b9..e06f228454 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretsManager.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretsManager.java
@@ -58,7 +58,7 @@ public class ConnectorTestRunnerSecretsManager implements SecretsManager {
return Optional.empty();
}
- final List<Secret> secrets = provider.getSecrets(List.of(secretReference.getSecretName()));
+ final List<Secret> secrets = provider.getSecrets(List.of(secretReference.getFullyQualifiedName()));
return secrets.isEmpty() ? Optional.empty() : Optional.of(secrets.getFirst());
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java
index 10d18823fd..297ddee44c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java
@@ -32,6 +32,7 @@ public class ConnectorValueReferenceDTO {
private String secretProviderId;
private String secretProviderName;
private String secretName;
+ private String fullyQualifiedSecretName;
/**
* @return the type of value (STRING_LITERAL, ASSET_REFERENCE, or SECRET_REFERENCE)
@@ -104,5 +105,14 @@ public class ConnectorValueReferenceDTO {
public void setSecretName(final String secretName) {
this.secretName = secretName;
}
+
+ public void setFullyQualifiedSecretName(final String fullyQualifiedSecretName) {
+ this.fullyQualifiedSecretName = fullyQualifiedSecretName;
+ }
+
+ @Schema(description = "The fully qualified secret name. Applicable when valueType is SECRET_REFERENCE.")
+ public String getFullyQualifiedSecretName() {
+ return fullyQualifiedSecretName;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java
index 780d496a14..8fdc58dd35 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java
@@ -21,35 +21,53 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.Response.Status.Family;
import jakarta.ws.rs.core.Response.StatusType;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.cluster.coordination.ClusterCoordinator;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.components.connector.ConnectorRequestReplicator;
import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Supplier;
public class ClusteredConnectorRequestReplicator implements ConnectorRequestReplicator {
private static final String GET = "GET";
private final Supplier<RequestReplicator> requestReplicatorSupplier;
+ private final Supplier<ClusterCoordinator> clusterCoordinatorSupplier;
+ private final String replicationScheme;
- public ClusteredConnectorRequestReplicator(final Supplier<RequestReplicator> requestReplicatorSupplier) {
+ public ClusteredConnectorRequestReplicator(final Supplier<RequestReplicator> requestReplicatorSupplier, final Supplier<ClusterCoordinator> clusterCoordinatorSupplier,
+ final boolean httpsEnabled) {
this.requestReplicatorSupplier = Objects.requireNonNull(requestReplicatorSupplier, "Request Replicator Supplier required");
+ this.clusterCoordinatorSupplier = Objects.requireNonNull(clusterCoordinatorSupplier, "Cluster Coordinator Supplier required");
+ this.replicationScheme = httpsEnabled ? "https" : "http";
}
@Override
public ConnectorState getState(final String connectorId) throws IOException {
final RequestReplicator requestReplicator = getRequestReplicator();
- final AsyncClusterResponse asyncResponse = requestReplicator.replicate(GET, URI.create("/nifi-api/connectors/" + connectorId + "/status"), null, Map.of());
+ final NiFiUser nodeUser = getNodeUser();
+ final AsyncClusterResponse asyncResponse = requestReplicator.replicate(nodeUser, GET, URI.create(replicationScheme + "://localhost/nifi-api/connectors/" + connectorId), Map.of(), Map.of());
try {
final Response response = asyncResponse.awaitMergedResponse().getClientResponse();
verifyResponse(response.getStatusInfo(), connectorId);
- // TODO: Need a ConnectorStatusEntity? Need correct URI.
- return response.readEntity(ConnectorState.class);
+ final ConnectorEntity connectorEntity = response.readEntity(ConnectorEntity.class);
+ final String stateName = connectorEntity.getComponent().getState();
+
+ try {
+ return ConnectorState.valueOf(stateName);
+ } catch (final IllegalArgumentException e) {
+ throw new IOException("Received unknown Connector state '" + stateName + "' for Connector with ID " + connectorId);
+ }
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while waiting for response for status of Connector with ID " + connectorId, e);
@@ -65,6 +83,18 @@ public class ClusteredConnectorRequestReplicator implements ConnectorRequestRepl
return Objects.requireNonNull(requestReplicator, "Request Replicator required");
}
+ private NiFiUser getNodeUser() {
+ final ClusterCoordinator clusterCoordinator = clusterCoordinatorSupplier.get();
+ Objects.requireNonNull(clusterCoordinator, "Cluster Coordinator required");
+
+ final NodeIdentifier localNodeIdentifier = clusterCoordinator.getLocalNodeIdentifier();
+ Objects.requireNonNull(localNodeIdentifier, "Local Node Identifier required");
+
+ final Set<String> nodeIdentities = localNodeIdentifier.getNodeIdentities();
+ final String nodeIdentity = nodeIdentities.isEmpty() ? localNodeIdentifier.getApiAddress() : nodeIdentities.iterator().next();
+ return new StandardNiFiUser.Builder().identity(nodeIdentity).build();
+ }
+
private void verifyResponse(final StatusType responseStatusType, final String connectorId) throws IOException {
final int statusCode = responseStatusType.getStatusCode();
final String reason = responseStatusType.getReasonPhrase();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java
index 9b26fe3629..5f880dfc3f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java
@@ -51,21 +51,52 @@ public class ConnectorEntityMerger {
return;
}
- // Basic connector state consistency check - all nodes should have same state
- for (final Map.Entry<NodeIdentifier, ConnectorDTO> nodeEntry : dtoMap.entrySet()) {
- final ConnectorDTO nodeConnector = nodeEntry.getValue();
+ mergeState(clientDto, dtoMap);
+ // Merge configuration steps to handle dynamic property descriptors
+ mergeActiveConfiguration(clientDto, dtoMap);
+ mergeWorkingConfiguration(clientDto, dtoMap);
+ }
+
+ /**
+ * Merges state from cluster nodes using priority-based selection.
+ * Priority (highest to lowest):
+ * 1. UPDATE_FAILED - indicates a failed update that needs attention
+ * 2. PREPARING_FOR_UPDATE - connector is preparing for update
+ * 3. UPDATING - connector is actively updating
+ * 4. UPDATED - connector has been updated
+ * 5. STARTING/STOPPING - connector is transitioning between run states
+ * 6. RUNNING/STOPPED/DISABLED - stable states
+ */
+ private static void mergeState(final ConnectorDTO clientDto, final Map<NodeIdentifier, ConnectorDTO> dtoMap) {
+ String mergedState = clientDto.getState();
+
+ for (final ConnectorDTO nodeConnector : dtoMap.values()) {
if (nodeConnector != null) {
- // Ensure state consistency across cluster
- if (clientDto.getState() == null && nodeConnector.getState() != null) {
- clientDto.setState(nodeConnector.getState());
+ final String nodeState = nodeConnector.getState();
+ if (nodeState != null) {
+ if (getStatePriority(nodeState) > getStatePriority(mergedState)) {
+ mergedState = nodeState;
+ }
}
}
}
- // Merge configuration steps to handle dynamic property descriptors
- mergeActiveConfiguration(clientDto, dtoMap);
- mergeWorkingConfiguration(clientDto, dtoMap);
+ clientDto.setState(mergedState);
+ }
+
+ private static int getStatePriority(final String state) {
+ if (state == null) {
+ return 0;
+ }
+ return switch (state) {
+ case "UPDATE_FAILED" -> 6;
+ case "PREPARING_FOR_UPDATE" -> 5;
+ case "UPDATING" -> 4;
+ case "UPDATED" -> 3;
+ case "STARTING", "STOPPING" -> 2;
+ default -> 1; // RUNNING, STOPPED, DISABLED
+ };
}
private static void mergeActiveConfiguration(final ConnectorDTO clientDto, final Map<NodeIdentifier, ConnectorDTO> dtoMap) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/ConnectorRequestReplicatorConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/ConnectorRequestReplicatorConfiguration.java
index 4024321bd1..747b253716 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/ConnectorRequestReplicatorConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/ConnectorRequestReplicatorConfiguration.java
@@ -17,6 +17,7 @@
package org.apache.nifi.framework.configuration;
+import org.apache.nifi.cluster.coordination.ClusterCoordinator;
import org.apache.nifi.cluster.coordination.http.replication.ClusteredConnectorRequestReplicator;
import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
import org.apache.nifi.components.connector.ConnectorRequestReplicator;
@@ -32,13 +33,16 @@ public class ConnectorRequestReplicatorConfiguration {
@Bean
public ConnectorRequestReplicator connectorRequestReplicator(
final NiFiProperties nifiProperties,
- final ObjectProvider<RequestReplicator> requestReplicatorProvider) {
+ final ObjectProvider<RequestReplicator> requestReplicatorProvider,
+ final ObjectProvider<ClusterCoordinator> clusterCoordinatorProvider) {
if (nifiProperties.isClustered()) {
// We have to use an ObjectProvider here and obtain a Supplier because of a circular dependency.
// The request replicator will not be available when created. However, it will be
// available before attempting to use the ConnectorRequestReplicator.
- return new ClusteredConnectorRequestReplicator(requestReplicatorProvider::getIfAvailable);
+ final String httpsHostname = nifiProperties.getProperty(NiFiProperties.WEB_HTTPS_HOST);
+ final boolean httpsEnabled = httpsHostname != null;
+ return new ClusteredConnectorRequestReplicator(requestReplicatorProvider::getIfAvailable, clusterCoordinatorProvider::getIfAvailable, httpsEnabled);
}
return new StandaloneConnectorRequestReplicator();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationTrigger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationTrigger.java
new file mode 100644
index 0000000000..a88cb0aeb4
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationTrigger.java
@@ -0,0 +1,56 @@
+/*
+ * 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.components.connector;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ExecutorService;
+import java.util.function.BooleanSupplier;
+
+/**
+ * Standard implementation of ConnectorValidationTrigger that submits validation
+ * tasks to an ExecutorService for asynchronous execution.
+ */
+public class StandardConnectorValidationTrigger implements ConnectorValidationTrigger {
+ private static final Logger logger = LoggerFactory.getLogger(StandardConnectorValidationTrigger.class);
+
+ private final ExecutorService threadPool;
+ private final BooleanSupplier flowInitialized;
+
+ public StandardConnectorValidationTrigger(final ExecutorService threadPool, final BooleanSupplier flowInitialized) {
+ this.threadPool = threadPool;
+ this.flowInitialized = flowInitialized;
+ }
+
+ @Override
+ public void triggerAsync(final ConnectorNode connector) {
+ if (!flowInitialized.getAsBoolean()) {
+ logger.debug("Triggered to perform validation on {} asynchronously but flow is not yet initialized so will ignore validation", connector);
+ return;
+ }
+
+ threadPool.submit(() -> trigger(connector));
+ }
+
+ @Override
+ public void trigger(final ConnectorNode connector) {
+ connector.performValidation();
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java
index b7d37efc19..c4d7563da0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java
@@ -62,9 +62,11 @@ public class ParameterProviderSecretProvider implements SecretProvider {
final ParameterDescriptor descriptor = parameter.getDescriptor();
return new StandardSecret.Builder()
+ .providerId(getProviderId())
.providerName(getProviderName())
.groupName(groupName)
.name(descriptor.getName())
+ .fullyQualifiedName(groupName + "." + descriptor.getName())
.description(descriptor.getDescription())
.value(parameter.getValue())
.authorizable(parameterProvider)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
index 50919c8b88..d4ba733b08 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
@@ -73,7 +73,7 @@ public class ParameterProviderSecretsManager implements SecretsManager {
return Optional.empty();
}
- final List<Secret> secrets = provider.getSecrets(List.of(secretReference.getSecretName()));
+ final List<Secret> secrets = provider.getSecrets(List.of(secretReference.getFullyQualifiedName()));
if (secrets.isEmpty()) {
return Optional.empty();
}
@@ -105,13 +105,13 @@ public class ParameterProviderSecretsManager implements SecretsManager {
}
final List<String> secretNames = new ArrayList<>();
- references.forEach(ref -> secretNames.add(ref.getSecretName()));
+ references.forEach(ref -> secretNames.add(ref.getFullyQualifiedName()));
final List<Secret> retrievedSecrets = provider.getSecrets(secretNames);
final Map<String, Secret> secretsByName = retrievedSecrets.stream()
- .collect(Collectors.toMap(Secret::getName, Function.identity()));
+ .collect(Collectors.toMap(Secret::getFullyQualifiedName, Function.identity()));
for (final SecretReference secretReference : references) {
- final Secret secret = secretsByName.get(secretReference.getSecretName());
+ final Secret secret = secretsByName.get(secretReference.getFullyQualifiedName());
secrets.put(secretReference, secret);
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java
index f2da84d233..0a81955fe5 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java
@@ -23,20 +23,29 @@ import org.apache.nifi.authorization.resource.Authorizable;
import java.util.Objects;
public class StandardSecret implements AuthorizableSecret {
+ private final String providerId;
private final String providerName;
private final String groupName;
private final String name;
+ private final String fullyQualifiedName;
private final String description;
private final String value;
private final Authorizable authorizable;
private StandardSecret(final Builder builder) {
+ this.providerId = builder.providerId;
this.providerName = builder.providerName;
this.groupName = builder.groupName;
this.name = builder.name;
this.description = builder.description;
this.value = builder.value;
this.authorizable = builder.authorizable;
+ this.fullyQualifiedName = builder.fullyQualifiedName == null ? groupName + "." + name : builder.fullyQualifiedName;
+ }
+
+ @Override
+ public String getProviderId() {
+ return providerId;
}
@Override
@@ -64,6 +73,11 @@ public class StandardSecret implements AuthorizableSecret {
return value;
}
+ @Override
+ public String getFullyQualifiedName() {
+ return fullyQualifiedName;
+ }
+
@Override
public String toString() {
return "StandardSecret[providerName=%s, groupName=%s, name=%s]".formatted(providerName, groupName, name);
@@ -99,13 +113,20 @@ public class StandardSecret implements AuthorizableSecret {
}
public static class Builder {
+ private String providerId;
private String providerName;
private String groupName;
private String name;
+ private String fullyQualifiedName;
private String description;
private String value;
private Authorizable authorizable;
+ public Builder providerId(final String providerId) {
+ this.providerId = providerId;
+ return this;
+ }
+
public Builder providerName(final String providerName) {
this.providerName = providerName;
return this;
@@ -136,19 +157,27 @@ public class StandardSecret implements AuthorizableSecret {
return this;
}
+ public Builder fullyQualifiedName(final String fullyQualifiedName) {
+ this.fullyQualifiedName = fullyQualifiedName;
+ return this;
+ }
+
public StandardSecret build() {
- if (providerName == null) {
- throw new IllegalStateException("Provider name is required");
- }
if (groupName == null) {
throw new IllegalStateException("Group name is required");
}
- if (name == null) {
- throw new IllegalStateException("Secret name is required");
- }
if (authorizable == null) {
throw new IllegalStateException("Authorizable is required");
}
+ if (name == null || name.isEmpty()) {
+ throw new IllegalStateException("Secret name must be provided");
+ }
+ if (fullyQualifiedName == null || fullyQualifiedName.isEmpty()) {
+ throw new IllegalStateException("Secret fully qualified name must be provided");
+ }
+ if ((providerId == null || providerId.isEmpty()) && (providerName == null || providerName.isEmpty())) {
+ throw new IllegalStateException("Either Secret provider ID or provider name must be provided");
+ }
return new StandardSecret(this);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
index e245dc68bf..1abafb7c93 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
@@ -1041,7 +1041,7 @@ public class VersionedComponentFlowMapper {
versionedConnector.setInstanceIdentifier(connectorNode.getIdentifier());
versionedConnector.setName(connectorNode.getName());
versionedConnector.setScheduledState(mapConnectorState(connectorNode.getDesiredState()));
- versionedConnector.setType(connectorNode.getComponentType());
+ versionedConnector.setType(connectorNode.getCanonicalClassName());
versionedConnector.setBundle(mapBundle(connectorNode.getBundleCoordinate()));
final List<VersionedConfigurationStep> activeFlowConfiguration = createVersionedConfigurationSteps(connectorNode.getActiveFlowContext());
@@ -1076,6 +1076,9 @@ public class VersionedComponentFlowMapper {
final Map<String, VersionedConnectorValueReference> versionedProperties = new HashMap<>();
for (final Map.Entry<String, ConnectorValueReference> entry : configuration.getPropertyValues().entrySet()) {
final ConnectorValueReference valueReference = entry.getValue();
+ if (valueReference == null) {
+ continue;
+ }
final VersionedConnectorValueReference versionedReference = new VersionedConnectorValueReference();
versionedReference.setValueType(valueReference.getValueType().name());
@@ -1087,11 +1090,13 @@ public class VersionedComponentFlowMapper {
versionedReference.setProviderId(secretRef.getProviderId());
versionedReference.setProviderName(secretRef.getProviderName());
versionedReference.setSecretName(secretRef.getSecretName());
+ versionedReference.setFullyQualifiedSecretName(secretRef.getFullyQualifiedName());
}
}
versionedProperties.put(entry.getKey(), versionedReference);
}
+
return versionedProperties;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java
index 51a50973ef..b193239ed3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java
@@ -98,7 +98,7 @@ public class TestParameterProviderSecretsManager {
when(node.fetchParameterValues(anyList())).thenAnswer(invocation -> {
final List<String> requestedNames = invocation.getArgument(0);
final List<Parameter> matchingParameters = parameterList.stream()
- .filter(p -> requestedNames.contains(p.getDescriptor().getName()))
+ .filter(p -> requestedNames.contains(groupName + "." + p.getDescriptor().getName()))
.toList();
if (matchingParameters.isEmpty()) {
return List.of();
@@ -111,21 +111,26 @@ public class TestParameterProviderSecretsManager {
private Parameter createParameter(final String name, final String description, final String value) {
final ParameterDescriptor descriptor = new ParameterDescriptor.Builder()
- .name(name)
- .description(description)
- .build();
+ .name(name)
+ .description(description)
+ .build();
return new Parameter.Builder()
- .descriptor(descriptor)
- .value(value)
- .build();
+ .descriptor(descriptor)
+ .value(value)
+ .build();
}
private SecretReference createSecretReference(final String providerId, final String providerName, final String secretName) {
- final SecretReference reference = mock(SecretReference.class);
- when(reference.getProviderId()).thenReturn(providerId);
- when(reference.getProviderName()).thenReturn(providerName);
- when(reference.getSecretName()).thenReturn(secretName);
- return reference;
+ final String groupName = getGroupName(secretName);
+ return new SecretReference(providerId, providerName, secretName, groupName + "." + secretName);
+ }
+
+ private String getGroupName(final String secretName) {
+ if (secretName.equals(SECRET_1_NAME) || secretName.equals(SECRET_2_NAME)) {
+ return GROUP_1_NAME;
+ }
+
+ return GROUP_2_NAME;
}
@Test
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index f61d35e3e0..b5c6d4820d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -21,6 +21,7 @@ import org.apache.nifi.authorization.resource.ComponentAuthorizable;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.VersionedComponent;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.components.validation.ValidationState;
@@ -32,6 +33,7 @@ import org.apache.nifi.flow.VersionedConfigurationStep;
import org.apache.nifi.logging.ComponentLog;
import java.time.Duration;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Future;
@@ -61,6 +63,8 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
String getComponentType();
+ String getCanonicalClassName();
+
BundleCoordinate getBundleCoordinate();
List<AllowableValue> fetchAllowableValues(String stepName, String propertyName);
@@ -134,6 +138,18 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
ValidationState performValidation();
+ /**
+ * Returns the current validation status of the connector.
+ * @return the current ValidationStatus
+ */
+ ValidationStatus getValidationStatus();
+
+ /**
+ * Returns the validation errors for the connector, or an empty collection if the connector is valid.
+ * @return the validation errors, or an empty collection if valid
+ */
+ Collection<ValidationResult> getValidationErrors();
+
/**
* Enables the Connector. This method should only be invoked via the ConnectorRepository.
*/
@@ -144,6 +160,8 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
void disable();
+ ValidationState getValidationState();
+
/**
* Returns an Optional Duration indicating how long the Connector has been idle (i.e., not processed any FlowFiles and with no FlowFiles queued).
* If there is any FlowFile queued, the Optional will be empty. Otherwise, the duration will indicate how long it has been since the
@@ -184,6 +202,8 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
void setConfiguration(String configurationStepName, StepConfiguration configuration) throws FlowUpdateException;
+ void transitionStateForUpdating();
+
void prepareForUpdate() throws FlowUpdateException;
/**
@@ -202,10 +222,12 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
/**
* Inherits the given flow configuration into this Connector's active flow configuration.
- * @param flowConfiguration the flow configuration to inherit
+ * @param activeFlowConfiguration the active flow configuration to inherit
+ * @param workingFlowConfiguration the working flow configuration to inherit
* @param flowContextBundle the bundle associated with the provided configuration
* @throws FlowUpdateException if unable to inherit the given flow configuration
* @throws IllegalStateException if the Connector is not in a state of UPDATING
*/
- void inheritConfiguration(List<VersionedConfigurationStep> flowConfiguration, Bundle flowContextBundle) throws FlowUpdateException;
+ void inheritConfiguration(List<VersionedConfigurationStep> activeFlowConfiguration, List<VersionedConfigurationStep> workingFlowConfiguration,
+ Bundle flowContextBundle) throws FlowUpdateException;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
index c951a29f59..be39e14dc6 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
@@ -78,9 +78,10 @@ public interface ConnectorRepository {
void configureConnector(ConnectorNode connector, String stepName, StepConfiguration configuration) throws FlowUpdateException;
- void applyUpdate(ConnectorNode connector) throws FlowUpdateException;
+ void applyUpdate(ConnectorNode connector, ConnectorUpdateContext context) throws FlowUpdateException;
- void inheritConfiguration(ConnectorNode connector, List<VersionedConfigurationStep> flowConfiguration, Bundle flowContextBundle) throws FlowUpdateException;
+ void inheritConfiguration(ConnectorNode connector, List<VersionedConfigurationStep> activeFlowConfiguration,
+ List<VersionedConfigurationStep> workingFlowConfiguration, Bundle flowContextBundle) throws FlowUpdateException;
SecretsManager getSecretsManager();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorUpdateContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorUpdateContext.java
new file mode 100644
index 0000000000..c5a00bb61b
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorUpdateContext.java
@@ -0,0 +1,26 @@
+/*
+ * 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.components.connector;
+
+import java.io.IOException;
+
+public interface ConnectorUpdateContext {
+
+ void saveFlow() throws IOException;
+
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorValidationTrigger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorValidationTrigger.java
new file mode 100644
index 0000000000..9cd4a13d7d
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorValidationTrigger.java
@@ -0,0 +1,39 @@
+/*
+ * 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.components.connector;
+
+/**
+ * Trigger for initiating validation of a ConnectorNode.
+ */
+public interface ConnectorValidationTrigger {
+
+ /**
+ * Triggers validation of the given connector to occur asynchronously.
+ *
+ * @param connector the connector to validate
+ */
+ void triggerAsync(ConnectorNode connector);
+
+ /**
+ * Triggers validation of the given connector immediately in the current thread.
+ *
+ * @param connector the connector to validate
+ */
+ void trigger(ConnectorNode connector);
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
index 975bc2aafa..e1722ff9a3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
@@ -202,7 +202,7 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
return null;
}
- final List<Secret> secrets = provider.getSecrets(List.of(secretReference.getSecretName()));
+ final List<Secret> secrets = provider.getSecrets(List.of(secretReference.getFullyQualifiedName()));
return secrets.isEmpty() ? null : secrets.getFirst().getValue();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 7e8839dedf..80b5d126c6 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -28,6 +28,7 @@ import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.DescribedValue;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.components.connector.components.ParameterContextFacade;
import org.apache.nifi.components.validation.DisabledServiceValidationResult;
import org.apache.nifi.components.validation.ValidationState;
import org.apache.nifi.components.validation.ValidationStatus;
@@ -50,6 +51,7 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Duration;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -57,11 +59,13 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
public class StandardConnectorNode implements ConnectorNode {
private static final Logger logger = LoggerFactory.getLogger(StandardConnectorNode.class);
@@ -72,12 +76,17 @@ public class StandardConnectorNode implements ConnectorNode {
private final Authorizable parentAuthorizable;
private final ConnectorDetails connectorDetails;
private final String componentType;
+ private final String componentCanonicalClass;
private final BundleCoordinate bundleCoordinate;
private final ConnectorStateTransition stateTransition;
private final AtomicReference<String> versionedComponentId = new AtomicReference<>();
private final FlowContextFactory flowContextFactory;
private final FrameworkFlowContext activeFlowContext;
+ private final AtomicReference<ValidationState> validationState = new AtomicReference<>(new ValidationState(ValidationStatus.VALIDATING, Collections.emptyList()));
+ private final ConnectorValidationTrigger validationTrigger;
+ private volatile boolean triggerValidation = true;
+
private volatile FrameworkFlowContext workingFlowContext;
private volatile String name;
@@ -85,9 +94,10 @@ public class StandardConnectorNode implements ConnectorNode {
public StandardConnectorNode(final String identifier, final FlowManager flowManager, final ExtensionManager extensionManager,
- final Authorizable parentAuthorizable, final ConnectorDetails connectorDetails, final String componentType,
+ final Authorizable parentAuthorizable, final ConnectorDetails connectorDetails, final String componentType, final String componentCanonicalClass,
final MutableConnectorConfigurationContext configurationContext,
- final ConnectorStateTransition stateTransition, final FlowContextFactory flowContextFactory) {
+ final ConnectorStateTransition stateTransition, final FlowContextFactory flowContextFactory,
+ final ConnectorValidationTrigger validationTrigger) {
this.identifier = identifier;
this.flowManager = flowManager;
@@ -95,9 +105,11 @@ public class StandardConnectorNode implements ConnectorNode {
this.parentAuthorizable = parentAuthorizable;
this.connectorDetails = connectorDetails;
this.componentType = componentType;
+ this.componentCanonicalClass = componentCanonicalClass;
this.bundleCoordinate = connectorDetails.getBundleCoordinate();
this.stateTransition = stateTransition;
this.flowContextFactory = flowContextFactory;
+ this.validationTrigger = validationTrigger;
this.name = connectorDetails.getConnector().getClass().getSimpleName();
@@ -116,7 +128,7 @@ public class StandardConnectorNode implements ConnectorNode {
}
@Override
- public void prepareForUpdate() throws FlowUpdateException {
+ public void transitionStateForUpdating() {
final ConnectorState initialState = getCurrentState();
if (initialState == ConnectorState.UPDATING || initialState == ConnectorState.PREPARING_FOR_UPDATE) {
return;
@@ -124,6 +136,18 @@ public class StandardConnectorNode implements ConnectorNode {
stateTransition.setDesiredState(ConnectorState.UPDATING);
stateTransition.setCurrentState(ConnectorState.PREPARING_FOR_UPDATE);
+ }
+
+ @Override
+ public void prepareForUpdate() throws FlowUpdateException {
+ if (getCurrentState() != ConnectorState.PREPARING_FOR_UPDATE) {
+ throw new IllegalStateException("Cannot prepare update for " + this + " because its state is currently " + getCurrentState()
+ + "; it must be PREPARING_FOR_UPDATE.");
+ }
+ if (getDesiredState() != ConnectorState.UPDATING) {
+ throw new IllegalStateException("Cannot prepare update for " + this + " because its desired state is currently " + getDesiredState()
+ + "; it must be UPDATING.");
+ }
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
getConnector().prepareForUpdate(workingFlowContext, activeFlowContext);
@@ -142,28 +166,42 @@ public class StandardConnectorNode implements ConnectorNode {
}
@Override
- public void inheritConfiguration(final List<VersionedConfigurationStep> flowConfiguration, final Bundle flowContextBundle) throws FlowUpdateException {
- final MutableConnectorConfigurationContext configurationContext = createConfigurationContext(flowConfiguration);
+ public void inheritConfiguration(final List<VersionedConfigurationStep> activeConfig, final List<VersionedConfigurationStep> workingConfig,
+ final Bundle flowContextBundle) throws FlowUpdateException {
+
+ final MutableConnectorConfigurationContext configurationContext = createConfigurationContext(activeConfig);
final FrameworkFlowContext inheritContext = flowContextFactory.createWorkingFlowContext(identifier,
connectorDetails.getComponentLog(), configurationContext, flowContextBundle);
+ // Apply the update for the active config
applyUpdate(inheritContext);
+
+ // Configure the working config but do not apply
+ for (final VersionedConfigurationStep step : workingConfig) {
+ final StepConfiguration stepConfig = createStepConfiguration(step);
+ setConfiguration(step.getName(), stepConfig);
+ }
+ }
+
+ private StepConfiguration createStepConfiguration(final VersionedConfigurationStep step) {
+ final Map<String, ConnectorValueReference> convertedProperties = new HashMap<>();
+ if (step.getProperties() != null) {
+ for (final Map.Entry<String, VersionedConnectorValueReference> entry : step.getProperties().entrySet()) {
+ final ConnectorValueReference valueReference = createValueReference(entry.getValue());
+ convertedProperties.put(entry.getKey(), valueReference);
+ }
+ }
+
+ return new StepConfiguration(convertedProperties);
}
private MutableConnectorConfigurationContext createConfigurationContext(final List<VersionedConfigurationStep> flowConfiguration) {
final StandardConnectorConfigurationContext configurationContext = new StandardConnectorConfigurationContext(
initializationContext.getAssetManager(), initializationContext.getSecretsManager());
- for (final VersionedConfigurationStep configStep : flowConfiguration) {
- final Map<String, ConnectorValueReference> convertedProperties = new HashMap<>();
- if (configStep.getProperties() != null) {
- for (final Map.Entry<String, VersionedConnectorValueReference> entry : configStep.getProperties().entrySet()) {
- final ConnectorValueReference valueReference = createValueReference(entry.getValue());
- convertedProperties.put(entry.getKey(), valueReference);
- }
- }
-
- configurationContext.setProperties(configStep.getName(), new StepConfiguration(convertedProperties));
+ for (final VersionedConfigurationStep versionedConfigStep : flowConfiguration) {
+ final StepConfiguration stepConfig = createStepConfiguration(versionedConfigStep);
+ configurationContext.setProperties(versionedConfigStep.getName(), stepConfig);
}
return configurationContext;
@@ -174,7 +212,8 @@ public class StandardConnectorNode implements ConnectorNode {
return switch (valueType) {
case STRING_LITERAL -> new StringLiteralValue(versionedReference.getValue());
case ASSET_REFERENCE -> new AssetReference(versionedReference.getAssetId());
- case SECRET_REFERENCE -> new SecretReference(versionedReference.getProviderId(), versionedReference.getProviderName(), versionedReference.getSecretName());
+ case SECRET_REFERENCE -> new SecretReference(versionedReference.getProviderId(), versionedReference.getProviderName(),
+ versionedReference.getSecretName(), versionedReference.getFullyQualifiedSecretName());
};
}
@@ -199,7 +238,11 @@ public class StandardConnectorNode implements ConnectorNode {
activeFlowContext.getConfigurationContext().replaceProperties(stepConfig.stepName(), stepConfig.configuration());
}
+ getComponentLog().info("Working Context has been applied to Active Context");
+
// The update has been completed. Tear down and recreate the working flow context to ensure it is in a clean state.
+
+ resetValidationState();
recreateWorkingFlowContext();
} catch (final Throwable t) {
logger.error("Failed to finish update for {}", this, t);
@@ -474,6 +517,11 @@ public class StandardConnectorNode implements ConnectorNode {
return componentType;
}
+ @Override
+ public String getCanonicalClassName() {
+ return componentCanonicalClass;
+ }
+
@Override
public BundleCoordinate getBundleCoordinate() {
return bundleCoordinate;
@@ -532,6 +580,7 @@ public class StandardConnectorNode implements ConnectorNode {
initializationContext.updateFlow(activeFlowContext, initialFlow);
}
+ resetValidationState();
recreateWorkingFlowContext();
}
@@ -539,32 +588,37 @@ public class StandardConnectorNode implements ConnectorNode {
destroyWorkingContext();
workingFlowContext = flowContextFactory.createWorkingFlowContext(identifier,
connectorDetails.getComponentLog(), activeFlowContext.getConfigurationContext(), activeFlowContext.getBundle());
+
+ getComponentLog().info("Working Flow Context has been recreated");
}
@Override
public void pauseValidationTrigger() {
- // No-op for ConnectorNode
+ triggerValidation = false;
}
@Override
public void resumeValidationTrigger() {
+ triggerValidation = true;
logger.debug("Resuming Triggering of Validation State for {}; Resetting validation state", this);
resetValidationState();
}
@Override
public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final StepConfiguration configurationOverrides) {
- final Map<String, String> resolvedPropertyOverrides = resolvePropertyReferences(configurationOverrides);
+ final List<SecretReference> invalidSecretRefs = new ArrayList<>();
+ final List<AssetReference> invalidAssetRefs = new ArrayList<>();
+ final Map<String, String> resolvedPropertyOverrides = resolvePropertyReferences(configurationOverrides, invalidSecretRefs, invalidAssetRefs);
final List<ConfigVerificationResult> results = new ArrayList<>();
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
- final DescribedValueProvider allowableValueProvider = (step, propertyName) ->
- fetchAllowableValues(step, propertyName, workingFlowContext);
+ final DescribedValueProvider allowableValueProvider = (step, propertyName) -> fetchAllowableValues(step, propertyName, workingFlowContext);
final MutableConnectorConfigurationContext configContext = workingFlowContext.getConfigurationContext().createWithOverrides(stepName, resolvedPropertyOverrides);
- final ConnectorValidationContext validationContext = new StandardConnectorValidationContext(
- configContext.toConnectorConfiguration(), allowableValueProvider, workingFlowContext.getParameterContext());
+ final ConnectorConfiguration connectorConfig = configContext.toConnectorConfiguration();
+ final ParameterContextFacade paramContext = workingFlowContext.getParameterContext();
+ final ConnectorValidationContext validationContext = new StandardConnectorValidationContext(connectorConfig, allowableValueProvider, paramContext);
final Optional<ConfigurationStep> optionalStep = getConfigurationStep(stepName);
if (optionalStep.isEmpty()) {
@@ -581,6 +635,13 @@ public class StandardConnectorNode implements ConnectorNode {
final List<ValidationResult> validationResults = new ArrayList<>();
validatePropertyReferences(configurationStep, configurationOverrides, validationResults);
+ // If there are any invalid secrets or assets referenced, add Validation Results for them.
+ addInvalidReferenceResults(validationResults, invalidSecretRefs, invalidAssetRefs);
+
+ // If there are any framework-level validation failures, we do not run the Connector-specific validation because
+ // doing so would mean that we must provide weak guarantees about the state of the configuration when the Connector's
+ // validation is invoked. But if there are no framework-level validation failures, we can proceed to invoke the
+ // Connector's validation logic.
if (validationResults.isEmpty()) {
final List<ValidationResult> implValidationResults = getConnector().validateConfigurationStep(configurationStep, configContext, validationContext);
validationResults.addAll(implValidationResults);
@@ -615,15 +676,50 @@ public class StandardConnectorNode implements ConnectorNode {
.build();
}
- private Map<String, String> resolvePropertyReferences(final StepConfiguration configurationOverrides) {
+ private Map<String, String> resolvePropertyReferences(final StepConfiguration configurationOverrides, final List<SecretReference> invalidSecretRefs,
+ final List<AssetReference> invalidAssetRefs) {
+
final Map<String, String> resolvedProperties = new HashMap<>();
try {
+ // Secret References can be expensive to lookup so we don't want to call getSecret() for each one. Instead, we
+ // want to find all Secrets by Provider and then call fetchSecrets() once per Provider.
+ final Set<SecretReference> secretReferences = configurationOverrides.getPropertyValues().values().stream()
+ .filter(Objects::nonNull)
+ .filter(ref -> ref.getValueType() == ConnectorValueType.SECRET_REFERENCE)
+ .map(ref -> (SecretReference) ref)
+ .collect(Collectors.toSet());
+
+ final Map<SecretReference, Secret> secretsByReference = initializationContext.getSecretsManager().getSecrets(secretReferences);
+ secretsByReference.forEach((ref, secret) -> {
+ if (secret == null) {
+ invalidSecretRefs.add(ref);
+ }
+ });
+
for (final Map.Entry<String, ConnectorValueReference> entry : configurationOverrides.getPropertyValues().entrySet()) {
final String propertyName = entry.getKey();
final ConnectorValueReference valueReference = entry.getValue();
+
+ if (valueReference == null) {
+ continue;
+ }
+
+ // We've already looked up secrets above, so use the cached value here.
+ if (valueReference.getValueType() == ConnectorValueType.SECRET_REFERENCE) {
+ final SecretReference secretReference = (SecretReference) valueReference;
+ final Secret secret = secretsByReference.get(secretReference);
+ final String resolvedValue = (secret == null) ? null : secret.getValue();
+ resolvedProperties.put(propertyName, resolvedValue);
+ continue;
+ }
+
final String resolvedValue = resolvePropertyReference(valueReference);
resolvedProperties.put(propertyName, resolvedValue);
+
+ if (resolvedValue == null && valueReference.getValueType() == ConnectorValueType.ASSET_REFERENCE) {
+ invalidAssetRefs.add((AssetReference) valueReference);
+ }
}
} catch (final IOException ioe) {
throw new UncheckedIOException("Failed to resolve Secret references for " + this, ioe);
@@ -758,7 +854,29 @@ public class StandardConnectorNode implements ConnectorNode {
}
private void resetValidationState() {
- // TODO: Implement
+ validationState.set(new ValidationState(ValidationStatus.VALIDATING, Collections.emptyList()));
+
+ if (triggerValidation && validationTrigger != null) {
+ validationTrigger.triggerAsync(this);
+ } else {
+ logger.debug("Reset validation state of {} but will not trigger async validation because trigger has been paused or is null", this);
+ }
+ }
+
+ @Override
+ public ValidationStatus getValidationStatus() {
+ return validationState.get().getStatus();
+ }
+
+ @Override
+ public ValidationState getValidationState() {
+ return validationState.get();
+ }
+
+ @Override
+ public Collection<ValidationResult> getValidationErrors() {
+ final ValidationState state = validationState.get();
+ return state.getValidationErrors();
}
@Override
@@ -783,22 +901,26 @@ public class StandardConnectorNode implements ConnectorNode {
}
}
+ final ValidationState resultState;
if (allResults.isEmpty()) {
- return new ValidationState(ValidationStatus.VALID, Collections.emptyList());
- }
-
- // Filter out any results that are 'valid' and any results that are invalid due to the fact that a Controller Service is disabled,
- // since these will not be relevant when started.
- final List<ValidationResult> relevantResults = allResults.stream()
- .filter(result -> !result.isValid())
- .filter(result -> !DisabledServiceValidationResult.isMatch(result))
- .toList();
-
- if (relevantResults.isEmpty()) {
- return new ValidationState(ValidationStatus.VALID, Collections.emptyList());
+ resultState = new ValidationState(ValidationStatus.VALID, Collections.emptyList());
+ } else {
+ // Filter out any results that are 'valid' and any results that are invalid due to the fact that a Controller Service is disabled,
+ // since these will not be relevant when started.
+ final List<ValidationResult> relevantResults = allResults.stream()
+ .filter(result -> !result.isValid())
+ .filter(result -> !DisabledServiceValidationResult.isMatch(result))
+ .toList();
+
+ if (relevantResults.isEmpty()) {
+ resultState = new ValidationState(ValidationStatus.VALID, Collections.emptyList());
+ } else {
+ resultState = new ValidationState(ValidationStatus.INVALID, relevantResults);
+ }
}
- return new ValidationState(ValidationStatus.INVALID, relevantResults);
+ validationState.set(resultState);
+ return resultState;
}
}
@@ -813,7 +935,32 @@ public class StandardConnectorNode implements ConnectorNode {
continue;
}
+ final StepConfiguration stepConfiguration = namedStepConfig.configuration();
validatePropertyReferences(step, namedStepConfig.configuration(), allResults);
+
+ // Check for invalid Secret and Asset references
+ final List<SecretReference> invalidSecrets = new ArrayList<>();
+ final List<AssetReference> invalidAssets = new ArrayList<>();
+ resolvePropertyReferences(stepConfiguration, invalidSecrets, invalidAssets);
+ addInvalidReferenceResults(allResults, invalidSecrets, invalidAssets);
+ }
+ }
+
+ private void addInvalidReferenceResults(final List<ValidationResult> results, final List<SecretReference> invalidSecretRefs, final List<AssetReference> invalidAssetRefs) {
+ for (final SecretReference invalidSecretRef : invalidSecretRefs) {
+ results.add(new ValidationResult.Builder()
+ .subject("Secret Reference")
+ .valid(false)
+ .explanation("The referenced secret [" + invalidSecretRef.getFullyQualifiedName() + "] could not be found")
+ .build());
+ }
+
+ for (final AssetReference invalidAssetRef : invalidAssetRefs) {
+ results.add(new ValidationResult.Builder()
+ .subject("Asset Reference")
+ .valid(false)
+ .explanation("The referenced asset [" + invalidAssetRef.getAssetIdentifier() + "] could not be found")
+ .build());
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index 4cb87da4cf..df31268c3c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -102,21 +103,44 @@ public class StandardConnectorRepository implements ConnectorRepository {
}
@Override
- public void applyUpdate(final ConnectorNode connector) throws FlowUpdateException {
+ public void applyUpdate(final ConnectorNode connector, final ConnectorUpdateContext context) throws FlowUpdateException {
final ConnectorState initialDesiredState = connector.getDesiredState();
- // Perform whatever preparation is necessary for the update. Default implementation is to stop the connector.
- connector.prepareForUpdate();
+ // Transition the connector's state to PREPARING_FOR_UPDATE before starting the background process.
+ // This allows us to ensure that if we poll and see the state in the same state it was in before that
+ // we know the update has already completed (successfully or otherwise).
+ connector.transitionStateForUpdating();
+
+ // Update connector in a background thread. This will handle transitioning the Connector state appropriately
+ // so that it's clear when the update has completed.
+ lifecycleExecutor.submit(() -> updateConnector(connector, initialDesiredState, context));
+ }
+ private void updateConnector(final ConnectorNode connector, final ConnectorState initialDesiredState, final ConnectorUpdateContext context) {
try {
+ // Perform whatever preparation is necessary for the update. Default implementation is to stop the connector.
+ connector.prepareForUpdate();
+
// Wait for Connector State to become UPDATING
- waitForState(connector, ConnectorState.UPDATING, Set.of(ConnectorState.PREPARING_FOR_UPDATE));
+ waitForState(connector, Set.of(ConnectorState.UPDATING), Set.of(ConnectorState.PREPARING_FOR_UPDATE));
// Apply the update to the connector.
connector.applyUpdate();
- // Wait for Connector State to become UPDATED
- waitForState(connector, ConnectorState.UPDATED, Set.of(ConnectorState.UPDATING));
+ // Now that the update has been applied, save the flow so that the updated configuration is persisted.
+ context.saveFlow();
+
+ // Wait for Connector State to become UPDATED, or to revert to the initial desired state because, depending upon timing,
+ // other nodes may have already seen the transition to UPDATED and moved the connector back to the initial desired state.
+ final Set<ConnectorState> desirableStates = new HashSet<>();
+ desirableStates.add(initialDesiredState);
+ desirableStates.add(ConnectorState.UPDATED);
+ if (initialDesiredState == ConnectorState.RUNNING) {
+ desirableStates.add(ConnectorState.STARTING);
+ } else if (initialDesiredState == ConnectorState.STOPPED) {
+ desirableStates.add(ConnectorState.STOPPING);
+ }
+ waitForState(connector, desirableStates, Set.of(ConnectorState.UPDATING));
// If the initial desired state was RUNNING, start the connector again. Otherwise, stop it.
// We don't simply leave it be as the prepareForUpdate / update may have changed the state of some components.
@@ -125,12 +149,16 @@ public class StandardConnectorRepository implements ConnectorRepository {
} else {
connector.stop(lifecycleExecutor);
}
+
+ // We've updated the state of the connector so save flow again
+ context.saveFlow();
} catch (final Exception e) {
+ logger.error("Failed to apply connector update for {}", connector, e);
connector.abortUpdate(e);
}
}
- private void waitForState(final ConnectorNode connector, final ConnectorState desiredState, final Set<ConnectorState> allowableStates)
+ private void waitForState(final ConnectorNode connector, final Set<ConnectorState> desiredStates, final Set<ConnectorState> allowableStates)
throws FlowUpdateException, IOException, InterruptedException {
// Wait for Connector State to become the desired state
@@ -138,24 +166,24 @@ public class StandardConnectorRepository implements ConnectorRepository {
final long startNanos = System.nanoTime();
while (true) {
final ConnectorState clusterState = requestReplicator.getState(connector.getIdentifier());
- if (clusterState == desiredState) {
- logger.info("State for {} is now {}", connector, desiredState);
+ if (desiredStates.contains(clusterState)) {
+ logger.info("State for {} is now {}", connector, clusterState);
break;
} else if (allowableStates.contains(clusterState)) {
final long elapsedSeconds = Duration.ofNanos(System.nanoTime() - startNanos).toSeconds();
if (++iterations % 10 == 0) {
- logger.info("Waiting for {} to transition to {}. Current state is {}; elapsed time = {} secs", connector, desiredState, clusterState, elapsedSeconds);
+ logger.info("Waiting for {} to transition to one of {}. Current state is {}; elapsed time = {} secs", connector, desiredStates, clusterState, elapsedSeconds);
} else {
- logger.debug("Waiting for {} to transition to {}. Current state is {}; elapsed time = {} secs", connector, desiredState, clusterState, elapsedSeconds);
+ logger.debug("Waiting for {} to transition to one of {}. Current state is {}; elapsed time = {} secs", connector, desiredStates, clusterState, elapsedSeconds);
}
Thread.sleep(Duration.ofSeconds(1));
continue;
} else if (clusterState == ConnectorState.UPDATE_FAILED) {
- throw new FlowUpdateException("State of " + connector + " transitioned to UPDATE_FAILED while waiting for state " + desiredState);
+ throw new FlowUpdateException("State of " + connector + " transitioned to UPDATE_FAILED while waiting for state to become one of " + desiredStates);
}
- throw new FlowUpdateException("While waiting for %s to transition to state of %s, connector transitioned to unexpected state: %s".formatted(connector, desiredState, clusterState) );
+ throw new FlowUpdateException("While waiting for %s to transition to state in set %s, connector transitioned to unexpected state: %s".formatted(connector, desiredStates, clusterState) );
}
}
@@ -165,11 +193,14 @@ public class StandardConnectorRepository implements ConnectorRepository {
}
@Override
- public void inheritConfiguration(final ConnectorNode connector, final List<VersionedConfigurationStep> flowConfiguration, final Bundle flowContextBundle) throws FlowUpdateException {
+ public void inheritConfiguration(final ConnectorNode connector, final List<VersionedConfigurationStep> activeFlowConfiguration,
+ final List<VersionedConfigurationStep> workingFlowConfiguration, final Bundle flowContextBundle) throws FlowUpdateException {
+
+ connector.transitionStateForUpdating();
connector.prepareForUpdate();
try {
- connector.inheritConfiguration(flowConfiguration, flowContextBundle);
+ connector.inheritConfiguration(activeFlowConfiguration, workingFlowConfiguration, flowContextBundle);
} catch (final Exception e) {
connector.abortUpdate(e);
throw e;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index 707bdb5266..fd6cff90c8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -33,6 +33,7 @@ import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorDetails;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorValidationTrigger;
import org.apache.nifi.components.connector.ConnectorPropertyGroup;
import org.apache.nifi.components.connector.ConnectorStateTransition;
import org.apache.nifi.components.connector.ConnectorValueReference;
@@ -145,6 +146,7 @@ public class ExtensionBuilder {
private MutableConnectorConfigurationContext activeConfigurationContext;
private ConnectorStateTransition connectorStateTransition;
private FrameworkConnectorInitializationContextBuilder connectorInitializationContextBuilder;
+ private ConnectorValidationTrigger connectorValidationTrigger;
public ExtensionBuilder type(final String type) {
this.type = type;
@@ -274,14 +276,19 @@ public class ExtensionBuilder {
return this;
}
- public ProcessorNode buildProcessor() {
- requireNonNull(identifier, "Processor ID");
- requireNonNull(type, "Processor Type");
- requireNonNull(bundleCoordinate, "Bundle Coordinate");
- requireNonNull(extensionManager, "Extension Manager");
- requireNonNull(serviceProvider, "Controller Service Provider");
- requireNonNull(nodeTypeProvider, "Node Type Provider");
- requireNonNull(reloadComponent, "Reload Component");
+ public ExtensionBuilder connectorValidationTrigger(final ConnectorValidationTrigger connectorValidationTrigger) {
+ this.connectorValidationTrigger = connectorValidationTrigger;
+ return this;
+ }
+
+ public ProcessorNode buildProcessor() {
+ requireNonNull(identifier, "Processor ID");
+ requireNonNull(type, "Processor Type");
+ requireNonNull(bundleCoordinate, "Bundle Coordinate");
+ requireNonNull(extensionManager, "Extension Manager");
+ requireNonNull(serviceProvider, "Controller Service Provider");
+ requireNonNull(nodeTypeProvider, "Node Type Provider");
+ requireNonNull(reloadComponent, "Reload Component");
boolean creationSuccessful = true;
final StandardLoggingContext loggingContext = new StandardLoggingContext();
@@ -512,17 +519,19 @@ public class ExtensionBuilder {
}
};
- final ConnectorNode connectorNode = new StandardConnectorNode(
- identifier,
- flowController.getFlowManager(),
- extensionManager,
- connectorsAuthorizable,
- connectorDetails,
- componentType,
- activeConfigurationContext,
- connectorStateTransition,
- flowContextFactory
- );
+ final ConnectorNode connectorNode = new StandardConnectorNode(
+ identifier,
+ flowController.getFlowManager(),
+ extensionManager,
+ connectorsAuthorizable,
+ connectorDetails,
+ componentType,
+ type,
+ activeConfigurationContext,
+ connectorStateTransition,
+ flowContextFactory,
+ connectorValidationTrigger
+ );
initializeDefaultValues(connector, connectorNode.getActiveFlowContext());
// TODO: If an Exception is thrown in the call to #initialize, we should create a Ghosted Connector
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index 876ec6a70c..8464c58d63 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -57,6 +57,8 @@ import org.apache.nifi.components.connector.secrets.ParameterProviderSecretsMana
import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.components.connector.secrets.SecretsManagerInitializationContext;
import org.apache.nifi.components.connector.secrets.StandardSecretsManagerInitializationContext;
+import org.apache.nifi.components.connector.ConnectorValidationTrigger;
+import org.apache.nifi.components.connector.StandardConnectorValidationTrigger;
import org.apache.nifi.components.monitor.LongRunningTaskMonitor;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.components.state.StateProvider;
@@ -336,6 +338,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
private final FlowEngine validationThreadPool;
private final FlowEngine flowAnalysisThreadPool;
private final ValidationTrigger validationTrigger;
+ private final ConnectorValidationTrigger connectorValidationTrigger;
private final ReloadComponent reloadComponent;
private final ProvenanceAuthorizableFactory provenanceAuthorizableFactory;
private final UserAwareEventAccess eventAccess;
@@ -671,6 +674,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
this.validationThreadPool = new FlowEngine(5, "Validate Components", true);
this.validationTrigger = new StandardValidationTrigger(validationThreadPool, this::isInitialized);
+ this.connectorValidationTrigger = new StandardConnectorValidationTrigger(validationThreadPool, this::isInitialized);
if (remoteInputSocketPort == null) {
LOG.info("Not enabling RAW Socket Site-to-Site functionality because nifi.remote.input.socket.port is not set");
@@ -1559,6 +1563,10 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
return validationTrigger;
}
+ public ConnectorValidationTrigger getConnectorValidationTrigger() {
+ return connectorValidationTrigger;
+ }
+
public PropertyEncryptor getEncryptor() {
return encryptor;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 804d0f732e..398cb085bc 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -781,6 +781,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
.flowController(flowController)
.connectorStateTransition(stateTransition)
.connectorInitializationContextBuilder(flowController.getConnectorRepository().createInitializationContextBuilder())
+ .connectorValidationTrigger(flowController.getConnectorValidationTrigger())
.buildConnector();
if (firstTimeAdded) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
index 1b7c3b91eb..339c9a5714 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
@@ -1064,9 +1064,18 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
}
private boolean isConnectorConfigurationUpdated(final ConnectorNode existingConnector, final VersionedConnector versionedConnector) {
- final ConnectorConfiguration existingConfiguration = existingConnector.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration();
+ final ConnectorConfiguration activeConfig = existingConnector.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration();
+ final List<VersionedConfigurationStep> versionedActiveConfig = versionedConnector.getActiveFlowConfiguration();
+ final boolean activeContextChanged = isConnectorConfigurationUpdated(activeConfig, versionedActiveConfig);
- final List<VersionedConfigurationStep> versionedConfigurationSteps = versionedConnector.getActiveFlowConfiguration();
+ final ConnectorConfiguration workingConfig = existingConnector.getWorkingFlowContext().getConfigurationContext().toConnectorConfiguration();
+ final List<VersionedConfigurationStep> versionedWorkingConfig = versionedConnector.getWorkingFlowConfiguration();
+ final boolean workingContextChanged = isConnectorConfigurationUpdated(workingConfig, versionedWorkingConfig);
+
+ return activeContextChanged || workingContextChanged;
+ }
+
+ private boolean isConnectorConfigurationUpdated(final ConnectorConfiguration existingConfiguration, final List<VersionedConfigurationStep> versionedConfigurationSteps) {
if (versionedConfigurationSteps == null || versionedConfigurationSteps.isEmpty()) {
return existingConfiguration != null && !existingConfiguration.getNamedStepConfigurations().isEmpty();
}
@@ -1157,7 +1166,9 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
// prepareForUpdate. If any fails, we can restore them and throw an Exception. We don't want to be throwing an Exception in the middle
// of updating the flow.
try {
- connectorRepository.inheritConfiguration(connectorNode, versionedConnector.getActiveFlowConfiguration(), versionedConnector.getBundle());
+ final List<VersionedConfigurationStep> activeFlowConfig = versionedConnector.getActiveFlowConfiguration();
+ final List<VersionedConfigurationStep> workingFlowConfig = versionedConnector.getWorkingFlowConfiguration();
+ connectorRepository.inheritConfiguration(connectorNode, activeFlowConfig, workingFlowConfig, versionedConnector.getBundle());
} catch (final FlowUpdateException e) {
throw new RuntimeException(connectorNode + " failed to inherit configuration", e);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
index d0cf34c6ea..6e9a437aa9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
@@ -25,6 +25,8 @@ import org.apache.nifi.components.connector.processors.DuplicateFlowFile;
import org.apache.nifi.components.connector.processors.LogFlowFileContents;
import org.apache.nifi.components.connector.processors.OverwriteFlowFile;
import org.apache.nifi.components.connector.processors.TerminateFlowFile;
+import org.apache.nifi.components.connector.secrets.ParameterProviderSecretsManager;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.components.connector.services.CounterService;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.components.validation.ValidationState;
@@ -117,6 +119,11 @@ public class StandardConnectorNodeIT {
when(controllerServiceProvider.disableControllerServicesAsync(anyCollection())).thenReturn(CompletableFuture.completedFuture(null));
connectorRepository = new StandardConnectorRepository();
+ final SecretsManager secretsManager = new ParameterProviderSecretsManager();
+ final ConnectorRepositoryInitializationContext repoInitContext = mock(ConnectorRepositoryInitializationContext.class);
+ when(repoInitContext.getSecretsManager()).thenReturn(secretsManager);
+ connectorRepository.initialize(repoInitContext);
+
final ExtensionDiscoveringManager extensionManager = new StandardExtensionDiscoveringManager();
final BulletinRepository bulletinRepository = mock(BulletinRepository.class);
final StateManagerProvider stateManagerProvider = new MockStateManagerProvider();
@@ -226,6 +233,7 @@ public class StandardConnectorNodeIT {
}
private void configure(final ConnectorNode connectorNode, final ConnectorConfiguration configuration) throws FlowUpdateException {
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
for (final NamedStepConfiguration stepConfig : configuration.getNamedStepConfigurations()) {
connectorNode.setConfiguration(stepConfig.stepName(), stepConfig.configuration());
@@ -347,7 +355,6 @@ public class StandardConnectorNodeIT {
// Because the component is being removed and there's data queued in its incoming connection, it should fail.
final ConnectorConfiguration removeLogConfiguration = createConnectorConfiguration("Second Iteration", 5, false, false);
- connectorNode.prepareForUpdate();
final Throwable cause = assertThrows(FlowUpdateException.class, () -> configure(connectorNode, removeLogConfiguration));
connectorNode.abortUpdate(cause);
@@ -362,7 +369,6 @@ public class StandardConnectorNodeIT {
SystemBundle.SYSTEM_BUNDLE_COORDINATE, true, true);
assertNotNull(connectorNode);
- connectorNode.prepareForUpdate();
assertEquals(List.of("File"), getConfigurationStepNames(connectorNode));
final Path tempFile = Files.createTempFile("StandardConnectorNodeIT", ".txt");
@@ -371,6 +377,7 @@ public class StandardConnectorNodeIT {
final ConnectorConfiguration configuration = createFileConfiguration(tempFile.toFile().getAbsolutePath());
configure(connectorNode, configuration);
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
assertEquals(List.of("File", "Colors"), getConfigurationStepNames(connectorNode));
@@ -395,7 +402,6 @@ public class StandardConnectorNodeIT {
SystemBundle.SYSTEM_BUNDLE_COORDINATE, true, true);
assertNotNull(connectorNode);
- connectorNode.prepareForUpdate();
assertEquals(List.of("File"), getConfigurationStepNames(connectorNode));
final ConnectorConfiguration configuration = createFileConfiguration("/non/existent/file");
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index 5147fb0d04..8b0b619cf5 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -40,6 +40,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.time.Duration;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -51,11 +52,11 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -143,7 +144,7 @@ public class TestStandardConnectorNode {
}
@Test
- public void testCannotStartFromDisabledState() {
+ public void testCannotStartFromDisabledState() throws FlowUpdateException {
final StandardConnectorNode connectorNode = createConnectorNode();
connectorNode.disable();
assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
@@ -152,7 +153,7 @@ public class TestStandardConnectorNode {
}
@Test
- public void testCannotTransitionFromDisabledToRunning() {
+ public void testCannotTransitionFromDisabledToRunning() throws FlowUpdateException {
final StandardConnectorNode connectorNode = createConnectorNode();
connectorNode.disable();
assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
@@ -163,7 +164,7 @@ public class TestStandardConnectorNode {
}
@Test
- public void testEnableFromDisabledState() {
+ public void testEnableFromDisabledState() throws FlowUpdateException {
final StandardConnectorNode connectorNode = createConnectorNode();
connectorNode.disable();
assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
@@ -174,7 +175,7 @@ public class TestStandardConnectorNode {
}
@Test
- public void testDisableFromStoppedState() {
+ public void testDisableFromStoppedState() throws FlowUpdateException {
final StandardConnectorNode connectorNode = createConnectorNode();
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
@@ -236,14 +237,14 @@ public class TestStandardConnectorNode {
}
@Test
- public void testVerifyCanDeleteWhenStopped() {
+ public void testVerifyCanDeleteWhenStopped() throws FlowUpdateException {
final StandardConnectorNode connectorNode = createConnectorNode();
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.verifyCanDelete();
}
@Test
- public void testVerifyCanDeleteWhenDisabled() {
+ public void testVerifyCanDeleteWhenDisabled() throws FlowUpdateException {
final StandardConnectorNode connectorNode = createConnectorNode();
connectorNode.disable();
assertEquals(ConnectorState.DISABLED, connectorNode.getCurrentState());
@@ -260,7 +261,7 @@ public class TestStandardConnectorNode {
}
@Test
- public void testVerifyCanStartWhenStopped() {
+ public void testVerifyCanStartWhenStopped() throws FlowUpdateException {
final StandardConnectorNode connectorNode = createConnectorNode();
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.verifyCanStart();
@@ -279,7 +280,7 @@ public class TestStandardConnectorNode {
}
@Test
- public void testStopAlreadyStoppedReturnsImmediately() {
+ public void testStopAlreadyStoppedReturnsImmediately() throws FlowUpdateException {
final StandardConnectorNode connectorNode = createConnectorNode();
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
@@ -346,6 +347,7 @@ public class TestStandardConnectorNode {
final ConnectorConfiguration newConfiguration = createTestConfiguration();
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("testGroup", createStepConfiguration());
connectorNode.applyUpdate();
@@ -362,6 +364,7 @@ public class TestStandardConnectorNode {
final ConnectorConfiguration newConfiguration = createTestConfiguration();
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("testGroup", createStepConfiguration());
connectorNode.applyUpdate();
@@ -374,6 +377,7 @@ public class TestStandardConnectorNode {
final StandardConnectorNode connectorNode = createConnectorNode();
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("step1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.applyUpdate();
@@ -381,6 +385,7 @@ public class TestStandardConnectorNode {
final ConnectorConfiguration newConfiguration = createTestConfiguration("step1", "prop1", "value2");
connectorNode.stop(scheduler).get(5, TimeUnit.SECONDS);
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("step1", createStepConfiguration(Map.of("prop1", "value2")));
connectorNode.applyUpdate();
@@ -392,6 +397,7 @@ public class TestStandardConnectorNode {
final StandardConnectorNode connectorNode = createConnectorNode();
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.applyUpdate();
@@ -400,6 +406,7 @@ public class TestStandardConnectorNode {
// Wait for Connector to fully stop
connectorNode.stop(scheduler).get(5, TimeUnit.SECONDS);
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.setConfiguration("configurationStep2", createStepConfiguration(Map.of("prop2", "value2")));
@@ -413,12 +420,14 @@ public class TestStandardConnectorNode {
final StandardConnectorNode connectorNode = createConnectorNode();
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.setConfiguration("configurationStep2", createStepConfiguration(Map.of("prop2", "value2")));
connectorNode.applyUpdate();
connectorNode.stop(scheduler).get(5, TimeUnit.SECONDS);
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.applyUpdate();
@@ -437,6 +446,7 @@ public class TestStandardConnectorNode {
final StandardConnectorNode connectorNode = createConnectorNode(trackingConnector);
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("testGroup", createStepConfiguration());
connectorNode.applyUpdate();
@@ -448,11 +458,13 @@ public class TestStandardConnectorNode {
final StandardConnectorNode connectorNode = createConnectorNode(trackingConnector);
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.applyUpdate();
trackingConnector.reset();
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value2")));
connectorNode.applyUpdate();
@@ -466,6 +478,7 @@ public class TestStandardConnectorNode {
final StandardConnectorNode connectorNode = createConnectorNode(validationFailingConnector);
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
final List<ConfigVerificationResult> results = connectorNode.verifyConfigurationStep("testStep", new StepConfiguration(Map.of("testProperty", new StringLiteralValue("invalidValue"))));
@@ -481,28 +494,52 @@ public class TestStandardConnectorNode {
assertEquals("The property value is invalid", failedResult.getExplanation());
}
- private StandardConnectorNode createConnectorNode() {
+ private StandardConnectorNode createConnectorNode() throws FlowUpdateException {
final SleepingConnector sleepingConnector = new SleepingConnector(Duration.ofMillis(1));
return createConnectorNode(sleepingConnector);
}
- private StandardConnectorNode createConnectorNode(final Connector connector) {
+ private StandardConnectorNode createConnectorNode(final Connector connector) throws FlowUpdateException {
final ConnectorStateTransition stateTransition = new StandardConnectorStateTransition("TestConnectorNode");
+ final ConnectorValidationTrigger validationTrigger = new SynchronousConnectorValidationTrigger();
final StandardConnectorNode node = new StandardConnectorNode(
"test-connector-id",
mock(FlowManager.class),
- extensionManager, null,
+ extensionManager,
+ null,
createConnectorDetails(connector),
"TestConnector",
+ connector.getClass().getCanonicalName(),
new StandardConnectorConfigurationContext(assetManager, secretsManager),
stateTransition,
- flowContextFactory);
+ flowContextFactory,
+ validationTrigger);
- node.initializeConnector(mock(FrameworkConnectorInitializationContext.class));
- assertDoesNotThrow(node::loadInitialFlow);
+ // mock secrets manager
+ final SecretsManager secretsManager = mock(SecretsManager.class);
+ when(secretsManager.getAllSecrets()).thenReturn(List.of());
+ when(secretsManager.getSecrets(anySet())).thenReturn(Collections.emptyMap());
+
+ final FrameworkConnectorInitializationContext initializationContext = mock(FrameworkConnectorInitializationContext.class);
+ when(initializationContext.getSecretsManager()).thenReturn(secretsManager);
+
+ node.initializeConnector(initializationContext);
+ node.loadInitialFlow();
return node;
}
+ private static class SynchronousConnectorValidationTrigger implements ConnectorValidationTrigger {
+ @Override
+ public void triggerAsync(final ConnectorNode connector) {
+ trigger(connector);
+ }
+
+ @Override
+ public void trigger(final ConnectorNode connector) {
+ connector.performValidation();
+ }
+ }
+
private ConnectorDetails createConnectorDetails(final Connector connector) {
final ComponentLog componentLog = new MockComponentLog("TestConnector", connector);
final BundleCoordinate bundleCoordinate = new BundleCoordinate("org.apache.nifi", "test-standard-connector-node", "1.0.0");
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 740c66fa27..6852c9ee7b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -79,6 +79,7 @@ import org.apache.nifi.components.RequiredPermission;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.components.validation.ValidationState;
@@ -3645,8 +3646,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final RevisionClaim claim = new StandardRevisionClaim(revision);
+ final ConnectorUpdateContext updateContext = controllerFacade::saveImmediate;
+
final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
- connectorDAO.applyConnectorUpdate(connectorId);
+ connectorDAO.applyConnectorUpdate(connectorId, updateContext);
final ConnectorNode node = connectorDAO.getConnector(connectorId);
final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 56067bdc5c..4d26d4d7ab 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -5240,6 +5240,10 @@ public final class DtoFactory {
dto.setId(connector.getIdentifier());
dto.setName(connector.getName());
+ final ValidationState validationState = connector.getValidationState();
+ dto.setValidationStatus(validationState.getStatus().name());
+ dto.setValidationErrors(convertValidationErrors(validationState.getValidationErrors()));
+
final String canonicalName = connector.getConnector().getClass().getCanonicalName();
dto.setType(canonicalName != null ? canonicalName : connector.getConnector().getClass().getName());
@@ -5345,6 +5349,7 @@ public final class DtoFactory {
dto.setSecretProviderId(secretRef.getProviderId());
dto.setSecretProviderName(secretRef.getProviderName());
dto.setSecretName(secretRef.getSecretName());
+ dto.setFullyQualifiedSecretName(secretRef.getFullyQualifiedName());
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 7bc8615c8b..c75e827375 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -972,6 +972,10 @@ public class ControllerFacade implements Authorizable {
flowService.saveFlowChanges(TimeUnit.SECONDS, writeDelaySeconds);
}
+ public void saveImmediate() throws IOException {
+ flowService.saveFlowChanges();
+ }
+
/**
* Returns the socket port that the local instance is listening on for
* Site-to-Site communications
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
index 2a3ef15075..53c87afc99 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
@@ -20,6 +20,7 @@ import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import java.util.List;
@@ -46,7 +47,7 @@ public interface ConnectorDAO {
void updateConnectorConfigurationStep(String id, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
- void applyConnectorUpdate(String id);
+ void applyConnectorUpdate(String id, ConnectorUpdateContext updateContext);
void verifyCanVerifyConfigurationStep(String id, String configurationStepName);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
index 13de988b8b..8dbdfa538a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
@@ -22,6 +22,7 @@ import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.ConnectorValueType;
import org.apache.nifi.components.connector.SecretReference;
@@ -153,15 +154,15 @@ public class StandardConnectorDAO implements ConnectorDAO {
return switch (valueType) {
case STRING_LITERAL -> new StringLiteralValue(dto.getValue());
case ASSET_REFERENCE -> new AssetReference(dto.getAssetIdentifier());
- case SECRET_REFERENCE -> new SecretReference(dto.getSecretProviderId(), dto.getSecretProviderName(), dto.getSecretName());
+ case SECRET_REFERENCE -> new SecretReference(dto.getSecretProviderId(), dto.getSecretProviderName(), dto.getSecretName(), dto.getFullyQualifiedSecretName());
};
}
@Override
- public void applyConnectorUpdate(final String id) {
+ public void applyConnectorUpdate(final String id, final ConnectorUpdateContext updateContext) {
final ConnectorNode connector = getConnector(id);
try {
- getConnectorRepository().applyUpdate(connector);
+ getConnectorRepository().applyUpdate(connector, updateContext);
} catch (final Exception e) {
throw new NiFiCoreException("Failed to apply connector update: " + e, e);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
index bb96b35271..19dff6703a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
@@ -19,6 +19,7 @@ package org.apache.nifi.web.dao.impl;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.web.NiFiCoreException;
@@ -53,6 +54,9 @@ class StandardConnectorDAOTest {
@Mock
private ConnectorNode connectorNode;
+ @Mock
+ private ConnectorUpdateContext connectorUpdateContext;
+
private static final String CONNECTOR_ID = "test-connector-id";
private static final String STEP_NAME = "test-step";
private static final String PROPERTY_NAME = "test-property";
@@ -69,10 +73,10 @@ class StandardConnectorDAOTest {
void testApplyConnectorUpdate() throws Exception {
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
- connectorDAO.applyConnectorUpdate(CONNECTOR_ID);
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID, connectorUpdateContext);
verify(connectorRepository).getConnector(CONNECTOR_ID);
- verify(connectorRepository).applyUpdate(connectorNode);
+ verify(connectorRepository).applyUpdate(connectorNode, connectorUpdateContext);
}
@Test
@@ -80,54 +84,54 @@ class StandardConnectorDAOTest {
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(null);
final ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () ->
- connectorDAO.applyConnectorUpdate(CONNECTOR_ID)
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID, connectorUpdateContext)
);
assertEquals("Could not find Connector with ID " + CONNECTOR_ID, exception.getMessage());
verify(connectorRepository).getConnector(CONNECTOR_ID);
- verify(connectorRepository, never()).applyUpdate(any(ConnectorNode.class));
+ verify(connectorRepository, never()).applyUpdate(any(ConnectorNode.class), any(ConnectorUpdateContext.class));
}
@Test
void testApplyConnectorUpdateWithFlowUpdateException() throws Exception {
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
- doThrow(new FlowUpdateException("Flow update failed")).when(connectorRepository).applyUpdate(connectorNode);
+ doThrow(new FlowUpdateException("Flow update failed")).when(connectorRepository).applyUpdate(connectorNode, connectorUpdateContext);
final NiFiCoreException exception = assertThrows(NiFiCoreException.class, () ->
- connectorDAO.applyConnectorUpdate(CONNECTOR_ID)
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID, connectorUpdateContext)
);
assertEquals("Failed to apply connector update: org.apache.nifi.components.connector.FlowUpdateException: Flow update failed", exception.getMessage());
verify(connectorRepository).getConnector(CONNECTOR_ID);
- verify(connectorRepository).applyUpdate(connectorNode);
+ verify(connectorRepository).applyUpdate(connectorNode, connectorUpdateContext);
}
@Test
void testApplyConnectorUpdateWithRuntimeException() throws Exception {
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
- doThrow(new RuntimeException("Test exception")).when(connectorRepository).applyUpdate(connectorNode);
+ doThrow(new RuntimeException("Test exception")).when(connectorRepository).applyUpdate(connectorNode, connectorUpdateContext);
final NiFiCoreException exception = assertThrows(NiFiCoreException.class, () ->
- connectorDAO.applyConnectorUpdate(CONNECTOR_ID)
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID, connectorUpdateContext)
);
assertEquals("Failed to apply connector update: java.lang.RuntimeException: Test exception", exception.getMessage());
verify(connectorRepository).getConnector(CONNECTOR_ID);
- verify(connectorRepository).applyUpdate(connectorNode);
+ verify(connectorRepository).applyUpdate(connectorNode, connectorUpdateContext);
}
@Test
void testApplyConnectorUpdateWithNullException() throws Exception {
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
- doThrow(new RuntimeException()).when(connectorRepository).applyUpdate(connectorNode);
+ doThrow(new RuntimeException()).when(connectorRepository).applyUpdate(connectorNode, connectorUpdateContext);
final NiFiCoreException exception = assertThrows(NiFiCoreException.class, () ->
- connectorDAO.applyConnectorUpdate(CONNECTOR_ID)
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID, connectorUpdateContext)
);
assertEquals("Failed to apply connector update: java.lang.RuntimeException", exception.getMessage());
verify(connectorRepository).getConnector(CONNECTOR_ID);
- verify(connectorRepository).applyUpdate(connectorNode);
+ verify(connectorRepository).applyUpdate(connectorNode, connectorUpdateContext);
}
@Test
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java
new file mode 100644
index 0000000000..5d927096db
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java
@@ -0,0 +1,120 @@
+/*
+ * 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.connectors.tests.system;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.components.ValidationContext;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.Validator;
+import org.apache.nifi.components.connector.AbstractConnector;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.PropertyType;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class NopConnector extends AbstractConnector {
+
+ private static final ConnectorPropertyDescriptor IGNORED_PROPERTY = new ConnectorPropertyDescriptor.Builder()
+ .name("Ignored Property")
+ .description("This property is ignored by the NopConnector.")
+ .required(false)
+ .type(PropertyType.STRING)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ private static final ConnectorPropertyGroup IGNORED_PROPERTY_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Ignored Property Group")
+ .description("This property group is ignored by the NopConnector.")
+ .properties(List.of(IGNORED_PROPERTY))
+ .build();
+
+ private static final ConnectorPropertyDescriptor SECRET_PROPERTY = new ConnectorPropertyDescriptor.Builder()
+ .name("Secret Property")
+ .description("This is a secret property that is ignored by the NopConnector.")
+ .required(false)
+ .type(PropertyType.SECRET)
+ .addValidator(new SuperSecretValidator())
+ .build();
+ private static final ConnectorPropertyGroup SECRET_PROPERTY_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Secret Property Group")
+ .description("This property group is ignored by the NopConnector.")
+ .properties(List.of(SECRET_PROPERTY))
+ .build();
+
+ private static final ConfigurationStep IGNORED_STEP = new ConfigurationStep.Builder()
+ .name("Ignored Step")
+ .propertyGroups(List.of(IGNORED_PROPERTY_GROUP, SECRET_PROPERTY_GROUP))
+ .build();
+
+ private final List<ConfigurationStep> configurationSteps = List.of(IGNORED_STEP);
+
+ @Override
+ protected void onStepConfigured(final String stepName, final FlowContext workingContext) {
+ }
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ final VersionedProcessGroup group = new VersionedProcessGroup();
+ group.setName("Nop Flow");
+
+ final VersionedExternalFlow flow = new VersionedExternalFlow();
+ flow.setParameterContexts(Collections.emptyMap());
+ flow.setFlowContents(group);
+ return flow;
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> propertyValueOverrides, final FlowContext flowContext) {
+ return List.of(new ConfigVerificationResult.Builder()
+ .outcome(Outcome.SUCCESSFUL)
+ .subject(stepName)
+ .verificationStepName("Nop Verification")
+ .explanation("Successful verification with properties: " + propertyValueOverrides)
+ .build());
+ }
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ return configurationSteps;
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingFlowContext, final FlowContext activeFlowContext) {
+ }
+
+ private static class SuperSecretValidator implements Validator {
+ @Override
+ public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
+ return new ValidationResult.Builder()
+ .subject(subject)
+ .input(input)
+ .explanation("Value must be 'supersecret'")
+ .valid(input.equals("supersecret"))
+ .build();
+ }
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/parameter/tests/system/PropertiesParameterProvider.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/parameter/tests/system/PropertiesParameterProvider.java
index 62ad10d7a7..7c78a79cb2 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/parameter/tests/system/PropertiesParameterProvider.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/parameter/tests/system/PropertiesParameterProvider.java
@@ -44,7 +44,7 @@ import java.util.stream.Collectors;
description = "Specifies parameters in a properties file format for the group")
public class PropertiesParameterProvider extends AbstractParameterProvider implements ParameterProvider {
- private PropertyDescriptor PARAMETERS = new PropertyDescriptor.Builder()
+ private static final PropertyDescriptor PARAMETERS = new PropertyDescriptor.Builder()
.name("parameters")
.displayName("Parameters")
.description("Specifies parameters in a properties file format")
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
new file mode 100644
index 0000000000..8231782690
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.nifi.connectors.tests.system.NopConnector
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
index faf20f35cd..28b43e150c 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
@@ -17,6 +17,7 @@
package org.apache.nifi.tests.system;
import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
+import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.controller.AbstractPort;
import org.apache.nifi.controller.queue.LoadBalanceCompression;
import org.apache.nifi.controller.queue.LoadBalanceStrategy;
@@ -27,6 +28,7 @@ import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol;
import org.apache.nifi.scheduling.ExecutionNode;
import org.apache.nifi.stream.io.StreamUtils;
import org.apache.nifi.toolkit.client.ConnectionClient;
+import org.apache.nifi.toolkit.client.ConnectorClient;
import org.apache.nifi.toolkit.client.NiFiClient;
import org.apache.nifi.toolkit.client.NiFiClientException;
import org.apache.nifi.toolkit.client.ProcessorClient;
@@ -34,8 +36,11 @@ import org.apache.nifi.toolkit.client.VersionsClient;
import org.apache.nifi.web.api.dto.AssetReferenceDTO;
import org.apache.nifi.web.api.dto.BundleDTO;
import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectableDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
import org.apache.nifi.web.api.dto.CounterDTO;
import org.apache.nifi.web.api.dto.CountersSnapshotDTO;
@@ -54,10 +59,12 @@ import org.apache.nifi.web.api.dto.PortDTO;
import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.dto.VerifyConfigRequestDTO;
+import org.apache.nifi.web.api.dto.VerifyConnectorConfigStepRequestDTO;
import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
import org.apache.nifi.web.api.dto.VersionedFlowDTO;
import org.apache.nifi.web.api.dto.flow.FlowDTO;
@@ -69,8 +76,10 @@ import org.apache.nifi.web.api.dto.status.ConnectionStatusSnapshotDTO;
import org.apache.nifi.web.api.dto.status.ProcessGroupStatusSnapshotDTO;
import org.apache.nifi.web.api.dto.status.ProcessorStatusSnapshotDTO;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
import org.apache.nifi.web.api.entity.ConnectionEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.ControllerServiceRunStatusEntity;
import org.apache.nifi.web.api.entity.ControllerServicesEntity;
@@ -113,6 +122,7 @@ import org.apache.nifi.web.api.entity.ReportingTasksEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
import org.apache.nifi.web.api.entity.VerifyConfigRequestEntity;
+import org.apache.nifi.web.api.entity.VerifyConnectorConfigStepRequestEntity;
import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
import org.apache.nifi.web.api.entity.VersionedFlowUpdateRequestEntity;
import org.junit.jupiter.api.Assertions;
@@ -163,6 +173,12 @@ public class NiFiClientUtil {
return client;
}
+ private ConnectorClient getConnectorClient() {
+ final ConnectorClient client = nifiClient.getConnectorClient();
+ client.acknowledgeDisconnectedNode();
+ return client;
+ }
+
public ProcessorEntity startProcessor(final ProcessorEntity currentEntity) throws NiFiClientException, IOException, InterruptedException {
waitForValidationCompleted(currentEntity);
@@ -225,6 +241,162 @@ public class NiFiClientUtil {
return type.substring(lastIndex + 1);
}
+ public ConnectorEntity createConnector(final String simpleTypeName) throws NiFiClientException, IOException {
+ return createConnector(NiFiSystemIT.TEST_CONNECTORS_PACKAGE + "." + simpleTypeName, NiFiSystemIT.NIFI_GROUP_ID, NiFiSystemIT.TEST_EXTENSIONS_ARTIFACT_ID, nifiVersion);
+ }
+
+ public ConnectorEntity createConnector(final String type, final String bundleGroupId, final String artifactId, final String version) throws NiFiClientException, IOException {
+ final ConnectorDTO dto = new ConnectorDTO();
+ dto.setType(type);
+
+ final BundleDTO bundle = new BundleDTO();
+ bundle.setGroup(bundleGroupId);
+ bundle.setArtifact(artifactId);
+ bundle.setVersion(version);
+ dto.setBundle(bundle);
+
+ final ConnectorEntity entity = new ConnectorEntity();
+ entity.setComponent(dto);
+ entity.setRevision(createNewRevision());
+ entity.setDisconnectedNodeAcknowledged(true);
+
+ final ConnectorEntity connector = getConnectorClient().createConnector(entity);
+ logger.info("Created Connector [type={}, id={}, name={}] for Test [{}]", simpleName(type), connector.getId(), connector.getComponent().getName(), testName);
+ return connector;
+ }
+
+ public ConfigurationStepEntity configureConnector(final String connectorId, final String configurationStepName,
+ final Map<String, String> properties) throws NiFiClientException, IOException {
+ final ConnectorEntity connectorEntity = getConnectorClient().getConnector(connectorId);
+ return configureConnector(connectorEntity, configurationStepName, properties);
+ }
+
+ public ConfigurationStepEntity configureConnector(final ConnectorEntity connectorEntity, final String configurationStepName,
+ final Map<String, String> properties) throws NiFiClientException, IOException {
+ final Map<String, ConnectorValueReferenceDTO> propertyValues = properties.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, entry -> createStringLiteralValueReference(entry.getValue())));
+
+ final PropertyGroupConfigurationDTO propertyGroupConfig = new PropertyGroupConfigurationDTO();
+ propertyGroupConfig.setPropertyValues(propertyValues);
+
+ final ConfigurationStepConfigurationDTO stepConfig = new ConfigurationStepConfigurationDTO();
+ stepConfig.setConfigurationStepName(configurationStepName);
+ stepConfig.setPropertyGroupConfigurations(Collections.singletonList(propertyGroupConfig));
+
+ final ConfigurationStepEntity stepEntity = new ConfigurationStepEntity();
+ stepEntity.setParentConnectorId(connectorEntity.getId());
+ stepEntity.setParentConnectorRevision(connectorEntity.getRevision());
+ stepEntity.setConfigurationStep(stepConfig);
+ stepEntity.setDisconnectedNodeAcknowledged(true);
+
+ final ConfigurationStepEntity result = getConnectorClient().updateConfigurationStep(stepEntity);
+ logger.info("Configured Connector [id={}, step={}] for Test [{}]", connectorEntity.getId(), configurationStepName, testName);
+ return result;
+ }
+
+ private ConnectorValueReferenceDTO createStringLiteralValueReference(final String value) {
+ final ConnectorValueReferenceDTO valueRef = new ConnectorValueReferenceDTO();
+ valueRef.setValueType("STRING_LITERAL");
+ valueRef.setValue(value);
+ return valueRef;
+ }
+
+ public ConnectorValueReferenceDTO createSecretValueReference(final String secretProviderId, final String secretName, final String fullyQualifiedSecretName) {
+ final ConnectorValueReferenceDTO valueRef = new ConnectorValueReferenceDTO();
+ valueRef.setValueType("SECRET_REFERENCE");
+ valueRef.setSecretProviderId(secretProviderId);
+ valueRef.setSecretName(secretName);
+ valueRef.setFullyQualifiedSecretName(fullyQualifiedSecretName);
+ return valueRef;
+ }
+
+ public ConfigurationStepEntity configureConnectorWithReferences(final String connectorId, final String configurationStepName,
+ final Map<String, ConnectorValueReferenceDTO> propertyValues) throws NiFiClientException, IOException {
+ final ConnectorEntity connectorEntity = getConnectorClient().getConnector(connectorId);
+ return configureConnectorWithReferences(connectorEntity, configurationStepName, propertyValues);
+ }
+
+ public ConfigurationStepEntity configureConnectorWithReferences(final ConnectorEntity connectorEntity, final String configurationStepName,
+ final Map<String, ConnectorValueReferenceDTO> propertyValues) throws NiFiClientException, IOException {
+ final PropertyGroupConfigurationDTO propertyGroupConfig = new PropertyGroupConfigurationDTO();
+ propertyGroupConfig.setPropertyValues(propertyValues);
+
+ final ConfigurationStepConfigurationDTO stepConfig = new ConfigurationStepConfigurationDTO();
+ stepConfig.setConfigurationStepName(configurationStepName);
+ stepConfig.setPropertyGroupConfigurations(Collections.singletonList(propertyGroupConfig));
+
+ final ConfigurationStepEntity stepEntity = new ConfigurationStepEntity();
+ stepEntity.setParentConnectorId(connectorEntity.getId());
+ stepEntity.setParentConnectorRevision(connectorEntity.getRevision());
+ stepEntity.setConfigurationStep(stepConfig);
+ stepEntity.setDisconnectedNodeAcknowledged(true);
+
+ final ConfigurationStepEntity result = getConnectorClient().updateConfigurationStep(stepEntity);
+ logger.info("Configured Connector [id={}, step={}] for Test [{}]", connectorEntity.getId(), configurationStepName, testName);
+ return result;
+ }
+
+ public void applyConnectorUpdate(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException, InterruptedException {
+ final ConnectorClient client = nifiClient.getConnectorClient();
+ final ConnectorEntity initialEntity = client.getConnector(connectorEntity.getId());
+ final ConnectorState initialState = ConnectorState.valueOf(initialEntity.getComponent().getState());
+ client.applyUpdate(connectorEntity);
+
+ while (true) {
+ final ConnectorEntity currentEntity = client.getConnector(connectorEntity.getId());
+ final String state = currentEntity.getComponent().getState();
+ final ConnectorState currentState = ConnectorState.valueOf(state);
+
+ switch (currentState) {
+ case UPDATE_FAILED:
+ throw new IllegalStateException("Connector failed to update");
+ case UPDATED:
+ case UPDATING:
+ case PREPARING_FOR_UPDATE:
+ logger.debug("Waiting for Connector [id={}] to finish updating (current state={})...", connectorEntity.getId(), state);
+ break;
+ default:
+ if (initialState == currentState) {
+ logger.info("Connector [id={}] has successfully updated and now has state of {}", connectorEntity.getId(), state);
+ return;
+ }
+ logger.debug("Waiting for Connector [id={}] to return to initial state of {} (current state={})...", connectorEntity.getId(), initialState, state);
+ break;
+ }
+
+ // Wait 250 milliseconds before polling again
+ Thread.sleep(250L);
+ }
+ }
+
+ public void waitForValidConnector(final String connectorId) throws NiFiClientException, IOException, InterruptedException {
+ waitForConnectorValidationStatus(connectorId, "VALID");
+ }
+
+ public void waitForInvalidConnector(final String connectorId) throws NiFiClientException, IOException, InterruptedException {
+ waitForConnectorValidationStatus(connectorId, "INVALID");
+ }
+
+ public void waitForConnectorValidationStatus(final String connectorId, final String expectedStatus) throws NiFiClientException, IOException, InterruptedException {
+ int iteration = 0;
+ while (true) {
+ final ConnectorEntity entity = getConnectorClient().getConnector(connectorId);
+ final String validationStatus = entity.getComponent().getValidationStatus();
+ if (expectedStatus.equals(validationStatus)) {
+ return;
+ }
+
+ if ("VALIDATING".equals(validationStatus)) {
+ logger.debug("Waiting for Connector {} to finish validating...", connectorId);
+ } else if (iteration++ % 30 == 0) { // Every 3 seconds log status
+ logger.info("Connector with ID {} has validation status {} but expected {}. Validation errors: {}",
+ connectorId, validationStatus, expectedStatus, entity.getComponent().getValidationErrors());
+ }
+
+ Thread.sleep(100L);
+ }
+ }
+
public ParameterProviderEntity createParameterProvider(final String simpleTypeName) throws NiFiClientException, IOException {
return createParameterProvider(NiFiSystemIT.TEST_PARAM_PROVIDERS_PACKAGE + "." + simpleTypeName, NiFiSystemIT.NIFI_GROUP_ID, NiFiSystemIT.TEST_EXTENSIONS_ARTIFACT_ID, nifiVersion);
}
@@ -1931,6 +2103,48 @@ public class NiFiClientUtil {
return results.getRequest().getResults();
}
+ public List<ConfigVerificationResultDTO> verifyConnectorStepConfig(final String connectorId, final String configurationStepName,
+ final Map<String, String> properties) throws NiFiClientException, IOException, InterruptedException {
+
+ final Map<String, ConnectorValueReferenceDTO> propertyValues = properties.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, entry -> createStringLiteralValueReference(entry.getValue())));
+
+ return verifyConnectorStepConfigWithReferences(connectorId, configurationStepName, propertyValues);
+ }
+
+ public List<ConfigVerificationResultDTO> verifyConnectorStepConfigWithReferences(final String connectorId, final String configurationStepName,
+ final Map<String, ConnectorValueReferenceDTO> propertyValues) throws NiFiClientException, IOException, InterruptedException {
+
+ final PropertyGroupConfigurationDTO propertyGroupConfig = new PropertyGroupConfigurationDTO();
+ propertyGroupConfig.setPropertyValues(propertyValues);
+
+ final ConfigurationStepConfigurationDTO stepConfig = new ConfigurationStepConfigurationDTO();
+ stepConfig.setConfigurationStepName(configurationStepName);
+ stepConfig.setPropertyGroupConfigurations(Collections.singletonList(propertyGroupConfig));
+
+ final VerifyConnectorConfigStepRequestDTO requestDto = new VerifyConnectorConfigStepRequestDTO();
+ requestDto.setConnectorId(connectorId);
+ requestDto.setConfigurationStepName(configurationStepName);
+ requestDto.setConfigurationStep(stepConfig);
+
+ final VerifyConnectorConfigStepRequestEntity verificationRequest = new VerifyConnectorConfigStepRequestEntity();
+ verificationRequest.setRequest(requestDto);
+
+ VerifyConnectorConfigStepRequestEntity results = getConnectorClient().submitConfigStepVerificationRequest(verificationRequest);
+ while (!results.getRequest().isComplete()) {
+ Thread.sleep(50L);
+ results = getConnectorClient().getConfigStepVerificationRequest(connectorId, configurationStepName, results.getRequest().getRequestId());
+ }
+
+ final String failureReason = results.getRequest().getFailureReason();
+ if (failureReason != null) {
+ throw new IllegalStateException("Configuration step verification failed: " + failureReason);
+ }
+
+ getConnectorClient().deleteConfigStepVerificationRequest(connectorId, configurationStepName, results.getRequest().getRequestId());
+
+ return results.getRequest().getResults();
+ }
public ReportingTaskEntity createReportingTask(final String type, final String bundleGroupId, final String artifactId, final String version)
throws NiFiClientException, IOException {
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
index d9d5e522d7..74999daff1 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
@@ -89,6 +89,7 @@ public abstract class NiFiSystemIT implements NiFiInstanceProvider {
public static final String TEST_PYTHON_EXTENSIONS_ARTIFACT_ID = "python-extensions";
public static final String TEST_PARAM_PROVIDERS_PACKAGE = "org.apache.nifi.parameter.tests.system";
public static final String TEST_PROCESSORS_PACKAGE = "org.apache.nifi.processors.tests.system";
+ public static final String TEST_CONNECTORS_PACKAGE = "org.apache.nifi.connectors.tests.system";
public static final String TEST_CS_PACKAGE = "org.apache.nifi.cs.tests.system";
public static final String TEST_REPORTING_TASK_PACKAGE = "org.apache.nifi.reporting";
public static final String TEST_FLOW_ANALYSIS_RULE_PACKAGE = "org.apache.nifi.flowanalysis";
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorIT.java
new file mode 100644
index 0000000000..2727203cf8
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorIT.java
@@ -0,0 +1,128 @@
+/*
+ * 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.tests.system.connectors;
+
+import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class ClusteredConnectorIT extends ConnectorCrudIT {
+ private static final Logger logger = LoggerFactory.getLogger(ClusteredConnectorIT.class);
+
+ @Override
+ public NiFiInstanceFactory getInstanceFactory() {
+ return createTwoNodeInstanceFactory();
+ }
+
+ @Test
+ public void testModifiedWhenDisconnected() throws NiFiClientException, IOException, InterruptedException {
+ // Create a new connector
+ final ConnectorEntity connector = getClientUtil().createConnector("NopConnector");
+ assertNotNull(connector);
+ logger.info("Created Connector with ID {}", connector.getId());
+
+ // Configure the connector with an initial value and apply the update
+ getClientUtil().configureConnector(connector, "Ignored Step", Map.of("Ignored Property", "Initial Value"));
+ getClientUtil().applyConnectorUpdate(connector);
+ logger.info("Configured Connector with initial value and applied the update");
+
+ // Make requests to each node to ensure that the config has been applied
+ switchClientToNode(1);
+ ConnectorEntity node1Connector = getNifiClient().getConnectorClient(DO_NOT_REPLICATE).getConnector(connector.getId());
+ assertActiveConfigurationValue(node1Connector, "Ignored Property", "Initial Value");
+ assertWorkingConfigurationValue(node1Connector, "Ignored Property", "Initial Value");
+
+ switchClientToNode(2);
+ ConnectorEntity node2Connector = getNifiClient().getConnectorClient(DO_NOT_REPLICATE).getConnector(connector.getId());
+ assertActiveConfigurationValue(node2Connector, "Ignored Property", "Initial Value");
+ assertWorkingConfigurationValue(node2Connector, "Ignored Property", "Initial Value");
+ logger.info("Validated initial configuration on both nodes");
+
+ // Disconnect node 2
+ switchClientToNode(1);
+ disconnectNode(2);
+ logger.info("Disconnected Node 2");
+
+ // Make changes on node 1 and apply the change
+ getClientUtil().configureConnector(connector.getId(), "Ignored Step", Map.of("Ignored Property", "Applied While Disconnected"));
+ getNifiClient().getConnectorClient().applyUpdate(connector);
+ logger.info("Applied configuration change on Node 1 while Node 2 is disconnected");
+
+ // Configure the connector to yet another value on node 1 but do NOT apply the change
+ getClientUtil().configureConnector(connector.getId(), "Ignored Step", Map.of("Ignored Property", "Working Only Value"));
+ logger.info("Configured working configuration on Node 1 without applying the change");
+
+ // Verify node 1 has the expected active and working configurations
+ node1Connector = getNifiClient().getConnectorClient(DO_NOT_REPLICATE).getConnector(connector.getId());
+ assertActiveConfigurationValue(node1Connector, "Ignored Property", "Applied While Disconnected");
+ assertWorkingConfigurationValue(node1Connector, "Ignored Property", "Working Only Value");
+ logger.info("Validated active and working configuration on Node 1");
+
+ // Reconnect Node 2
+ reconnectNode(2);
+ waitForAllNodesConnected();
+ logger.info("Reconnected Node 2");
+
+ // Make requests to node 2 to ensure that it properly inherited both the working and active context configuration
+ switchClientToNode(2);
+ waitFor(() -> {
+ try {
+ final ConnectorEntity latestNode2Connector = getNifiClient().getConnectorClient(DO_NOT_REPLICATE).getConnector(connector.getId());
+ final String activeValue = getConfigurationValue(latestNode2Connector.getComponent().getActiveConfiguration(), "Ignored Property");
+ final String workingValue = getConfigurationValue(latestNode2Connector.getComponent().getWorkingConfiguration(), "Ignored Property");
+ return "Applied While Disconnected".equals(activeValue) && "Working Only Value".equals(workingValue);
+ } catch (final Exception e) {
+ return false;
+ }
+ });
+ logger.info("Validated that Node 2 has received updated configuration after reconnection");
+
+ final ConnectorEntity finalNode2Connector = getNifiClient().getConnectorClient(DO_NOT_REPLICATE).getConnector(connector.getId());
+ assertActiveConfigurationValue(finalNode2Connector, "Ignored Property", "Applied While Disconnected");
+ assertWorkingConfigurationValue(finalNode2Connector, "Ignored Property", "Working Only Value");
+ logger.info("Validated active and working configuration on Node 2");
+ }
+
+ private void assertActiveConfigurationValue(final ConnectorEntity connector, final String propertyName, final String expectedValue) {
+ final String actualValue = getConfigurationValue(connector.getComponent().getActiveConfiguration(), propertyName);
+ assertEquals(expectedValue, actualValue, "Active configuration property '" + propertyName + "' did not match expected value");
+ }
+
+ private void assertWorkingConfigurationValue(final ConnectorEntity connector, final String propertyName, final String expectedValue) {
+ final String actualValue = getConfigurationValue(connector.getComponent().getWorkingConfiguration(), propertyName);
+ assertEquals(expectedValue, actualValue, "Working configuration property '" + propertyName + "' did not match expected value");
+ }
+
+ private String getConfigurationValue(final ConnectorConfigurationDTO configuration, final String propertyName) {
+ final Map<String, ConnectorValueReferenceDTO> propertyValues = configuration.getConfigurationStepConfigurations().getFirst()
+ .getPropertyGroupConfigurations().getFirst().getPropertyValues();
+ final ConnectorValueReferenceDTO valueRef = propertyValues.get(propertyName);
+ return valueRef == null ? null : valueRef.getValue();
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
new file mode 100644
index 0000000000..0179c17496
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
@@ -0,0 +1,146 @@
+/*
+ * 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.tests.system.connectors;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ParameterProviderEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ConnectorCrudIT extends NiFiSystemIT {
+
+ @Test
+ public void testCreateConfigureRestart() throws NiFiClientException, IOException, InterruptedException {
+ // Create Connector
+ final ConnectorEntity connector = getClientUtil().createConnector("NopConnector");
+ assertNotNull(connector);
+
+ // Configure the connector and apply the configuration
+ getClientUtil().configureConnector(connector, "Ignored Step", Map.of("Ignored Property", "Hello, World!"));
+ getClientUtil().applyConnectorUpdate(connector);
+
+ // Configure with a different value
+ getClientUtil().configureConnector(connector, "Ignored Step", Map.of("Ignored Property", "Hola, Mundo!"));
+
+ // Restart NiFi and ensure that we have the expected values for both the active and working configurations
+ getNiFiInstance().stop();
+ getNiFiInstance().start();
+
+ // If running in cluster, wait for all nodes to be connected
+ if (getNumberOfNodes() > 1) {
+ waitForAllNodesConnected();
+ }
+
+ final ConnectorEntity connectorAfterRestart = getNifiClient().getConnectorClient().getConnector(connector.getId());
+ assertNotNull(connectorAfterRestart);
+
+ final ConnectorConfigurationDTO activeConfig = connectorAfterRestart.getComponent().getActiveConfiguration();
+ final Map<String, ConnectorValueReferenceDTO> activeProperties = activeConfig.getConfigurationStepConfigurations().getFirst().getPropertyGroupConfigurations().getFirst().getPropertyValues();
+ final String activeIgnoredProperty = activeProperties.get("Ignored Property").getValue();
+ assertEquals("Hello, World!", activeIgnoredProperty);
+
+ final ConnectorConfigurationDTO workingConfig = connectorAfterRestart.getComponent().getWorkingConfiguration();
+ final Map<String, ConnectorValueReferenceDTO> workingProperties = workingConfig.getConfigurationStepConfigurations().getFirst().getPropertyGroupConfigurations().getFirst().getPropertyValues();
+ final String workingIgnoredProperty = workingProperties.get("Ignored Property").getValue();
+ assertEquals("Hola, Mundo!", workingIgnoredProperty);
+ }
+
+ @Test
+ public void testConfigVerification() throws NiFiClientException, IOException, InterruptedException {
+ final ConnectorEntity connector = getClientUtil().createConnector("NopConnector");
+
+ final List<ConfigVerificationResultDTO> resultDtos = getClientUtil().verifyConnectorStepConfig(connector.getId(), "Ignored Step",
+ Map.of("Ignored Property", "Test Value"));
+ assertNotNull(resultDtos);
+ assertEquals(2, resultDtos.size());
+
+ assertTrue(resultDtos.stream().allMatch(result -> Outcome.SUCCESSFUL.name().equals(result.getOutcome())));
+ assertEquals("Property Validation", resultDtos.getFirst().getVerificationStepName());
+
+ final ConfigVerificationResultDTO resultDto = resultDtos.get(1);
+ assertEquals("Nop Verification", resultDto.getVerificationStepName());
+ assertTrue(resultDto.getExplanation().contains("Test Value"));
+ }
+
+ @Test
+ public void testSecretReferences() throws NiFiClientException, IOException, InterruptedException {
+ // Create and configure a Parameter Provider with two secrets
+ final ParameterProviderEntity paramProvider = getClientUtil().createParameterProvider("PropertiesParameterProvider");
+ getClientUtil().updateParameterProviderProperties(paramProvider, Map.of("parameters", "supersecret=supersecret\nother=other"));
+
+ // Create the Nop Connector
+ final ConnectorEntity connector = getClientUtil().createConnector("NopConnector");
+ assertNotNull(connector);
+
+ // Verify that using a String Literal for a SECRET property should fail validation
+ final Map<String, String> stringLiteralProperties = Map.of("Secret Property", "supersecret");
+ List<ConfigVerificationResultDTO> verificationResults = getClientUtil().verifyConnectorStepConfig(connector.getId(), "Ignored Step", stringLiteralProperties);
+ assertTrue(verificationResults.stream().anyMatch(result -> Outcome.FAILED.name().equals(result.getOutcome())));
+
+ // Configure and apply the String Literal value, then wait for invalid
+ getClientUtil().configureConnector(connector, "Ignored Step", stringLiteralProperties);
+ getClientUtil().applyConnectorUpdate(connector);
+ getClientUtil().waitForInvalidConnector(connector.getId());
+
+ // Verify that using a Secret Reference to 'other' (value "other") should fail validation
+ final ConnectorValueReferenceDTO otherSecretRef = getClientUtil().createSecretValueReference(paramProvider.getId(), "other", "Parameters.other");
+ final Map<String, ConnectorValueReferenceDTO> otherSecretProperties = Map.of("Secret Property", otherSecretRef);
+ verificationResults = getClientUtil().verifyConnectorStepConfigWithReferences(connector.getId(), "Ignored Step", otherSecretProperties);
+ assertTrue(verificationResults.stream().anyMatch(result -> Outcome.FAILED.name().equals(result.getOutcome())));
+
+ // Configure and apply the 'other' secret reference, then wait for invalid
+ getClientUtil().configureConnectorWithReferences(connector.getId(), "Ignored Step", otherSecretProperties);
+ getClientUtil().applyConnectorUpdate(connector);
+ getClientUtil().waitForInvalidConnector(connector.getId());
+
+ // Verify that using an invalid reference should fail validation
+ final ConnectorValueReferenceDTO invalidRef = getClientUtil().createSecretValueReference(paramProvider.getId(), "nonexistent", "Parameters.nonexistent");
+ final Map<String, ConnectorValueReferenceDTO> invalidSecretProperties = Map.of("Secret Property", invalidRef);
+ verificationResults = getClientUtil().verifyConnectorStepConfigWithReferences(connector.getId(), "Ignored Step", invalidSecretProperties);
+ assertTrue(verificationResults.stream().anyMatch(result -> Outcome.FAILED.name().equals(result.getOutcome())));
+
+ // Configure and apply the invalid secret reference, then wait for invalid
+ getClientUtil().configureConnectorWithReferences(connector.getId(), "Ignored Step", invalidSecretProperties);
+ getClientUtil().applyConnectorUpdate(connector);
+ getClientUtil().waitForInvalidConnector(connector.getId());
+
+ // Verify that using a Secret Reference to 'supersecret' (value "supersecret") should pass validation
+ final ConnectorValueReferenceDTO supersecretRef = getClientUtil().createSecretValueReference(paramProvider.getId(), "supersecret", "Parameters.supersecret");
+ final Map<String, ConnectorValueReferenceDTO> supersecretProperties = Map.of("Secret Property", supersecretRef);
+ verificationResults = getClientUtil().verifyConnectorStepConfigWithReferences(connector.getId(), "Ignored Step", supersecretProperties);
+ assertTrue(verificationResults.stream().allMatch(result -> Outcome.SUCCESSFUL.name().equals(result.getOutcome())));
+
+ // Configure and apply the 'supersecret' secret reference, then wait for valid
+ getClientUtil().configureConnectorWithReferences(connector.getId(), "Ignored Step", supersecretProperties);
+ getClientUtil().applyConnectorUpdate(connector);
+ getClientUtil().waitForValidConnector(connector.getId());
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiClientFactory.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiClientFactory.java
index 3c9d439f67..c33ca2fa66 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiClientFactory.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/NiFiClientFactory.java
@@ -23,6 +23,7 @@ import org.apache.nifi.toolkit.cli.api.ClientFactory;
import org.apache.nifi.toolkit.cli.impl.command.CommandOption;
import org.apache.nifi.toolkit.client.AccessClient;
import org.apache.nifi.toolkit.client.ConnectionClient;
+import org.apache.nifi.toolkit.client.ConnectorClient;
import org.apache.nifi.toolkit.client.ControllerClient;
import org.apache.nifi.toolkit.client.ControllerServicesClient;
import org.apache.nifi.toolkit.client.CountersClient;
@@ -412,6 +413,16 @@ public class NiFiClientFactory implements ClientFactory<NiFiClient> {
return wrappedClient.getSystemsDiagnosticsClient(requestConfig);
}
+ @Override
+ public ConnectorClient getConnectorClient() {
+ return wrappedClient.getConnectorClient();
+ }
+
+ @Override
+ public ConnectorClient getConnectorClient(final RequestConfig requestConfig) {
+ return wrappedClient.getConnectorClient(requestConfig);
+ }
+
@Override
public void close() throws IOException {
wrappedClient.close();
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
new file mode 100644
index 0000000000..504558232e
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
@@ -0,0 +1,260 @@
+/*
+ * 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.toolkit.client;
+
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
+import org.apache.nifi.web.api.entity.VerifyConnectorConfigStepRequestEntity;
+
+import java.io.IOException;
+
+/**
+ * Client for interacting with the Connector REST endpoints.
+ */
+public interface ConnectorClient {
+
+ /**
+ * Creates a new connector.
+ *
+ * @param connectorEntity the connector entity to create
+ * @return the created connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity createConnector(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Gets a connector by ID.
+ *
+ * @param connectorId the connector ID
+ * @return the connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity getConnector(String connectorId) throws NiFiClientException, IOException;
+
+ /**
+ * Updates a connector.
+ *
+ * @param connectorEntity the connector entity with updates
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity updateConnector(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Deletes a connector.
+ *
+ * @param connectorId the connector ID
+ * @param clientId the client ID
+ * @param version the revision version
+ * @return the deleted connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity deleteConnector(String connectorId, String clientId, long version) throws NiFiClientException, IOException;
+
+ /**
+ * Deletes a connector using the information from the entity.
+ *
+ * @param connectorEntity the connector entity to delete
+ * @return the deleted connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity deleteConnector(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Starts a connector.
+ *
+ * @param connectorId the connector ID
+ * @param clientId the client ID
+ * @param version the revision version
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity startConnector(String connectorId, String clientId, long version) throws NiFiClientException, IOException;
+
+ /**
+ * Starts a connector using the information from the entity.
+ *
+ * @param connectorEntity the connector entity to start
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity startConnector(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Stops a connector.
+ *
+ * @param connectorId the connector ID
+ * @param clientId the client ID
+ * @param version the revision version
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity stopConnector(String connectorId, String clientId, long version) throws NiFiClientException, IOException;
+
+ /**
+ * Stops a connector using the information from the entity.
+ *
+ * @param connectorEntity the connector entity to stop
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity stopConnector(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Gets the configuration step names for a connector.
+ *
+ * @param connectorId the connector ID
+ * @return the configuration step names entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConfigurationStepNamesEntity getConfigurationSteps(String connectorId) throws NiFiClientException, IOException;
+
+ /**
+ * Gets a specific configuration step for a connector.
+ *
+ * @param connectorId the connector ID
+ * @param configurationStepName the configuration step name
+ * @return the configuration step entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConfigurationStepEntity getConfigurationStep(String connectorId, String configurationStepName) throws NiFiClientException, IOException;
+
+ /**
+ * Gets the allowable values for a property in a configuration step.
+ *
+ * @param connectorId the connector ID
+ * @param configurationStepName the configuration step name
+ * @param propertyGroupName the property group name
+ * @param propertyName the property name
+ * @param filter optional filter for the allowable values
+ * @return the allowable values entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorPropertyAllowableValuesEntity getPropertyAllowableValues(String connectorId, String configurationStepName,
+ String propertyGroupName, String propertyName, String filter) throws NiFiClientException, IOException;
+
+ /**
+ * Updates a configuration step for a connector.
+ *
+ * @param configurationStepEntity the configuration step entity with updates
+ * @return the updated configuration step entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConfigurationStepEntity updateConfigurationStep(ConfigurationStepEntity configurationStepEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Submits a configuration step verification request.
+ *
+ * @param requestEntity the verification request entity
+ * @return the verification request entity with status
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ VerifyConnectorConfigStepRequestEntity submitConfigStepVerificationRequest(VerifyConnectorConfigStepRequestEntity requestEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Gets a configuration step verification request.
+ *
+ * @param connectorId the connector ID
+ * @param configurationStepName the configuration step name
+ * @param requestId the verification request ID
+ * @return the verification request entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ VerifyConnectorConfigStepRequestEntity getConfigStepVerificationRequest(String connectorId, String configurationStepName,
+ String requestId) throws NiFiClientException, IOException;
+
+ /**
+ * Deletes a configuration step verification request.
+ *
+ * @param connectorId the connector ID
+ * @param configurationStepName the configuration step name
+ * @param requestId the verification request ID
+ * @return the deleted verification request entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ VerifyConnectorConfigStepRequestEntity deleteConfigStepVerificationRequest(String connectorId, String configurationStepName,
+ String requestId) throws NiFiClientException, IOException;
+
+ /**
+ * Applies an update to a connector.
+ *
+ * @param connectorEntity the connector entity with revision
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity applyUpdate(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Gets the flow for the process group managed by a connector.
+ *
+ * @param connectorId the connector ID
+ * @return the process group flow entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ProcessGroupFlowEntity getFlow(String connectorId) throws NiFiClientException, IOException;
+
+ /**
+ * Gets the flow for the process group managed by a connector.
+ *
+ * @param connectorId the connector ID
+ * @param uiOnly whether to return only UI-specific fields
+ * @return the process group flow entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ProcessGroupFlowEntity getFlow(String connectorId, boolean uiOnly) throws NiFiClientException, IOException;
+
+ /**
+ * Gets the status for the process group managed by a connector.
+ *
+ * @param connectorId the connector ID
+ * @param recursive whether to include status for all descendant components
+ * @return the process group status entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ProcessGroupStatusEntity getStatus(String connectorId, boolean recursive) throws NiFiClientException, IOException;
+
+ /**
+ * Indicates that mutable requests should indicate that the client has
+ * acknowledged that the node is disconnected.
+ */
+ void acknowledgeDisconnectedNode();
+}
+
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/NiFiClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/NiFiClient.java
index 44bc180e3a..a70421202a 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/NiFiClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/NiFiClient.java
@@ -135,12 +135,18 @@ public interface NiFiClient extends Closeable {
SnippetClient getSnippetClient(RequestConfig requestConfig);
- // ----- SnippetClient -----
+ // ----- SystemDiagnosticsClient -----
SystemDiagnosticsClient getSystemsDiagnosticsClient();
SystemDiagnosticsClient getSystemsDiagnosticsClient(RequestConfig requestConfig);
+ // ----- ConnectorClient -----
+
+ ConnectorClient getConnectorClient();
+
+ ConnectorClient getConnectorClient(RequestConfig requestConfig);
+
/**
* The builder interface that implementations should provide for obtaining the
* client.
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
new file mode 100644
index 0000000000..0c184aa125
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
@@ -0,0 +1,387 @@
+/*
+ * 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.toolkit.client.impl;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.MediaType;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.toolkit.client.ConnectorClient;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.toolkit.client.RequestConfig;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
+import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
+import org.apache.nifi.web.api.entity.VerifyConnectorConfigStepRequestEntity;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Jersey implementation of ConnectorClient.
+ */
+public class JerseyConnectorClient extends AbstractJerseyClient implements ConnectorClient {
+
+ private final WebTarget connectorsTarget;
+ private volatile WebTarget connectorTarget;
+
+ public JerseyConnectorClient(final WebTarget baseTarget) {
+ this(baseTarget, null);
+ }
+
+ public JerseyConnectorClient(final WebTarget baseTarget, final RequestConfig requestConfig) {
+ super(requestConfig);
+ this.connectorsTarget = baseTarget.path("/connectors");
+ this.connectorTarget = baseTarget.path("/connectors/{id}");
+ }
+
+ @Override
+ public void acknowledgeDisconnectedNode() {
+ connectorTarget = connectorTarget.queryParam("disconnectedNodeAcknowledged", true);
+ }
+
+ @Override
+ public ConnectorEntity createConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException {
+ if (connectorEntity == null) {
+ throw new IllegalArgumentException("Connector entity cannot be null");
+ }
+
+ return executeAction("Error creating Connector", () -> {
+ return getRequestBuilder(connectorsTarget).post(
+ Entity.entity(connectorEntity, MediaType.APPLICATION_JSON_TYPE),
+ ConnectorEntity.class);
+ });
+ }
+
+ @Override
+ public ConnectorEntity getConnector(final String connectorId) throws NiFiClientException, IOException {
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector ID cannot be null or blank");
+ }
+
+ return executeAction("Error retrieving Connector", () -> {
+ final WebTarget target = connectorTarget.resolveTemplate("id", connectorId);
+ return getRequestBuilder(target).get(ConnectorEntity.class);
+ });
+ }
+
+ @Override
+ public ConnectorEntity updateConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException {
+ if (connectorEntity == null) {
+ throw new IllegalArgumentException("Connector entity cannot be null");
+ }
+
+ return executeAction("Error updating Connector", () -> {
+ final WebTarget target = connectorTarget.resolveTemplate("id", connectorEntity.getId());
+ return getRequestBuilder(target).put(
+ Entity.entity(connectorEntity, MediaType.APPLICATION_JSON_TYPE),
+ ConnectorEntity.class);
+ });
+ }
+
+ @Override
+ public ConnectorEntity deleteConnector(final String connectorId, final String clientId, final long version) throws NiFiClientException, IOException {
+ return deleteConnector(connectorId, clientId, version, false);
+ }
+
+ private ConnectorEntity deleteConnector(final String connectorId, final String clientId, final long version,
+ final Boolean acknowledgeDisconnect) throws NiFiClientException, IOException {
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector ID cannot be null or blank");
+ }
+
+ return executeAction("Error deleting Connector", () -> {
+ WebTarget target = connectorTarget
+ .queryParam("version", version)
+ .queryParam("clientId", clientId)
+ .resolveTemplate("id", connectorId);
+
+ if (acknowledgeDisconnect == Boolean.TRUE) {
+ target = target.queryParam("disconnectedNodeAcknowledged", "true");
+ }
+
+ return getRequestBuilder(target).delete(ConnectorEntity.class);
+ });
+ }
+
+ @Override
+ public ConnectorEntity deleteConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException {
+ if (connectorEntity == null) {
+ throw new IllegalArgumentException("Connector entity cannot be null");
+ }
+ return deleteConnector(connectorEntity.getId(), connectorEntity.getRevision().getClientId(),
+ connectorEntity.getRevision().getVersion(), connectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ @Override
+ public ConnectorEntity startConnector(final String connectorId, final String clientId, final long version) throws NiFiClientException, IOException {
+ return updateConnectorRunStatus(connectorId, "RUNNING", clientId, version, false);
+ }
+
+ @Override
+ public ConnectorEntity startConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException {
+ return updateConnectorRunStatus(connectorEntity.getId(), "RUNNING", connectorEntity.getRevision().getClientId(),
+ connectorEntity.getRevision().getVersion(), connectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ @Override
+ public ConnectorEntity stopConnector(final String connectorId, final String clientId, final long version) throws NiFiClientException, IOException {
+ return updateConnectorRunStatus(connectorId, "STOPPED", clientId, version, false);
+ }
+
+ @Override
+ public ConnectorEntity stopConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException {
+ return updateConnectorRunStatus(connectorEntity.getId(), "STOPPED", connectorEntity.getRevision().getClientId(),
+ connectorEntity.getRevision().getVersion(), connectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ private ConnectorEntity updateConnectorRunStatus(final String connectorId, final String desiredState, final String clientId,
+ final long version, final Boolean disconnectedNodeAcknowledged) throws NiFiClientException, IOException {
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector ID cannot be null or blank");
+ }
+
+ return executeAction("Error updating Connector run status", () -> {
+ final WebTarget target = connectorTarget
+ .path("/run-status")
+ .resolveTemplate("id", connectorId);
+
+ final ConnectorRunStatusEntity runStatusEntity = new ConnectorRunStatusEntity();
+ runStatusEntity.setState(desiredState);
+ runStatusEntity.setDisconnectedNodeAcknowledged(disconnectedNodeAcknowledged);
+
+ final RevisionDTO revisionDto = new RevisionDTO();
+ revisionDto.setClientId(clientId);
+ revisionDto.setVersion(version);
+ runStatusEntity.setRevision(revisionDto);
+
+ return getRequestBuilder(target).put(
+ Entity.entity(runStatusEntity, MediaType.APPLICATION_JSON_TYPE),
+ ConnectorEntity.class);
+ });
+ }
+
+ @Override
+ public ConfigurationStepNamesEntity getConfigurationSteps(final String connectorId) throws NiFiClientException, IOException {
+ Objects.requireNonNull(connectorId, "Connector ID required");
+
+ return executeAction("Error retrieving configuration steps", () -> {
+ final WebTarget target = connectorTarget
+ .path("/configuration-steps")
+ .resolveTemplate("id", connectorId);
+ return getRequestBuilder(target).get(ConfigurationStepNamesEntity.class);
+ });
+ }
+
+ @Override
+ public ConfigurationStepEntity getConfigurationStep(final String connectorId, final String configurationStepName) throws NiFiClientException, IOException {
+ Objects.requireNonNull(connectorId, "Connector ID required");
+ Objects.requireNonNull(configurationStepName, "Configuration step name required");
+
+ return executeAction("Error retrieving configuration step", () -> {
+ final WebTarget target = connectorTarget
+ .path("/configuration-steps/{configurationStepName}")
+ .resolveTemplate("id", connectorId)
+ .resolveTemplate("configurationStepName", configurationStepName);
+ return getRequestBuilder(target).get(ConfigurationStepEntity.class);
+ });
+ }
+
+ @Override
+ public ConnectorPropertyAllowableValuesEntity getPropertyAllowableValues(final String connectorId, final String configurationStepName,
+ final String propertyGroupName, final String propertyName, final String filter) throws NiFiClientException, IOException {
+ Objects.requireNonNull(connectorId, "Connector ID required");
+ Objects.requireNonNull(configurationStepName, "Configuration step name required");
+ Objects.requireNonNull(propertyGroupName, "Property group name required");
+ Objects.requireNonNull(propertyName, "Property name required");
+
+ return executeAction("Error retrieving property allowable values", () -> {
+ WebTarget target = connectorTarget
+ .path("/configuration-steps/{configurationStepName}/property-groups/{propertyGroupName}/properties/{propertyName}/allowable-values")
+ .resolveTemplate("id", connectorId)
+ .resolveTemplate("configurationStepName", configurationStepName)
+ .resolveTemplate("propertyGroupName", propertyGroupName)
+ .resolveTemplate("propertyName", propertyName);
+
+ if (filter != null) {
+ target = target.queryParam("filter", filter);
+ }
+
+ return getRequestBuilder(target).get(ConnectorPropertyAllowableValuesEntity.class);
+ });
+ }
+
+ @Override
+ public ConfigurationStepEntity updateConfigurationStep(final ConfigurationStepEntity configurationStepEntity) throws NiFiClientException, IOException {
+ if (configurationStepEntity == null) {
+ throw new IllegalArgumentException("Configuration step entity cannot be null");
+ }
+ if (configurationStepEntity.getParentConnectorId() == null) {
+ throw new IllegalArgumentException("Parent connector ID cannot be null");
+ }
+ if (configurationStepEntity.getConfigurationStep() == null) {
+ throw new IllegalArgumentException("Configuration step cannot be null");
+ }
+ if (configurationStepEntity.getConfigurationStep().getConfigurationStepName() == null) {
+ throw new IllegalArgumentException("Configuration step name cannot be null");
+ }
+
+ return executeAction("Error updating configuration step", () -> {
+ final WebTarget target = connectorTarget
+ .path("/configuration-steps/{configurationStepName}")
+ .resolveTemplate("id", configurationStepEntity.getParentConnectorId())
+ .resolveTemplate("configurationStepName", configurationStepEntity.getConfigurationStep().getConfigurationStepName());
+
+ return getRequestBuilder(target).put(
+ Entity.entity(configurationStepEntity, MediaType.APPLICATION_JSON_TYPE),
+ ConfigurationStepEntity.class);
+ });
+ }
+
+ @Override
+ public VerifyConnectorConfigStepRequestEntity submitConfigStepVerificationRequest(final VerifyConnectorConfigStepRequestEntity requestEntity)
+ throws NiFiClientException, IOException {
+ if (requestEntity == null) {
+ throw new IllegalArgumentException("Verification request entity cannot be null");
+ }
+ if (requestEntity.getRequest() == null) {
+ throw new IllegalArgumentException("Verification request DTO cannot be null");
+ }
+ if (requestEntity.getRequest().getConnectorId() == null) {
+ throw new IllegalArgumentException("Connector ID cannot be null");
+ }
+ if (requestEntity.getRequest().getConfigurationStepName() == null) {
+ throw new IllegalArgumentException("Configuration step name cannot be null");
+ }
+
+ return executeAction("Error submitting configuration step verification request", () -> {
+ final WebTarget target = connectorTarget
+ .path("/configuration-steps/{configurationStepName}/verify-config")
+ .resolveTemplate("id", requestEntity.getRequest().getConnectorId())
+ .resolveTemplate("configurationStepName", requestEntity.getRequest().getConfigurationStepName());
+
+ return getRequestBuilder(target).post(
+ Entity.entity(requestEntity, MediaType.APPLICATION_JSON_TYPE),
+ VerifyConnectorConfigStepRequestEntity.class);
+ });
+ }
+
+ @Override
+ public VerifyConnectorConfigStepRequestEntity getConfigStepVerificationRequest(final String connectorId, final String configurationStepName,
+ final String requestId) throws NiFiClientException, IOException {
+ Objects.requireNonNull(connectorId, "Connector ID required");
+ Objects.requireNonNull(configurationStepName, "Configuration step name required");
+ Objects.requireNonNull(requestId, "Request ID required");
+
+ return executeAction("Error retrieving configuration step verification request", () -> {
+ final WebTarget target = connectorTarget
+ .path("/configuration-steps/{configurationStepName}/verify-config/{requestId}")
+ .resolveTemplate("id", connectorId)
+ .resolveTemplate("configurationStepName", configurationStepName)
+ .resolveTemplate("requestId", requestId);
+
+ return getRequestBuilder(target).get(VerifyConnectorConfigStepRequestEntity.class);
+ });
+ }
+
+ @Override
+ public VerifyConnectorConfigStepRequestEntity deleteConfigStepVerificationRequest(final String connectorId, final String configurationStepName,
+ final String requestId) throws NiFiClientException, IOException {
+ Objects.requireNonNull(connectorId, "Connector ID required");
+ Objects.requireNonNull(configurationStepName, "Configuration step name required");
+ Objects.requireNonNull(requestId, "Request ID required");
+
+ return executeAction("Error deleting configuration step verification request", () -> {
+ final WebTarget target = connectorTarget
+ .path("/configuration-steps/{configurationStepName}/verify-config/{requestId}")
+ .resolveTemplate("id", connectorId)
+ .resolveTemplate("configurationStepName", configurationStepName)
+ .resolveTemplate("requestId", requestId);
+
+ return getRequestBuilder(target).delete(VerifyConnectorConfigStepRequestEntity.class);
+ });
+ }
+
+ @Override
+ public ConnectorEntity applyUpdate(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException {
+ if (connectorEntity == null) {
+ throw new IllegalArgumentException("Connector entity cannot be null");
+ }
+ if (connectorEntity.getRevision() == null) {
+ throw new IllegalArgumentException("Revision cannot be null");
+ }
+
+ final String connectorId = connectorEntity.getId();
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector ID cannot be null or blank");
+ }
+
+ return executeAction("Error applying connector update", () -> {
+ final WebTarget target = connectorTarget
+ .path("/apply-update")
+ .resolveTemplate("id", connectorId);
+
+ return getRequestBuilder(target).post(
+ Entity.entity(connectorEntity, MediaType.APPLICATION_JSON_TYPE),
+ ConnectorEntity.class);
+ });
+ }
+
+ @Override
+ public ProcessGroupFlowEntity getFlow(final String connectorId) throws NiFiClientException, IOException {
+ return getFlow(connectorId, false);
+ }
+
+ @Override
+ public ProcessGroupFlowEntity getFlow(final String connectorId, final boolean uiOnly) throws NiFiClientException, IOException {
+ Objects.requireNonNull(connectorId, "Connector ID required");
+
+ return executeAction("Error retrieving connector flow", () -> {
+ WebTarget target = connectorTarget
+ .path("/flow")
+ .resolveTemplate("id", connectorId);
+
+ if (uiOnly) {
+ target = target.queryParam("uiOnly", "true");
+ }
+
+ return getRequestBuilder(target).get(ProcessGroupFlowEntity.class);
+ });
+ }
+
+ @Override
+ public ProcessGroupStatusEntity getStatus(final String connectorId, final boolean recursive) throws NiFiClientException, IOException {
+ Objects.requireNonNull(connectorId, "Connector ID required");
+
+ return executeAction("Error retrieving connector status", () -> {
+ WebTarget target = connectorTarget
+ .path("/status")
+ .resolveTemplate("id", connectorId);
+
+ if (recursive) {
+ target = target.queryParam("recursive", "true");
+ }
+
+ return getRequestBuilder(target).get(ProcessGroupStatusEntity.class);
+ });
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyNiFiClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyNiFiClient.java
index af5697e262..024b9f4a60 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyNiFiClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyNiFiClient.java
@@ -26,6 +26,7 @@ import jakarta.ws.rs.client.WebTarget;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.toolkit.client.AccessClient;
import org.apache.nifi.toolkit.client.ConnectionClient;
+import org.apache.nifi.toolkit.client.ConnectorClient;
import org.apache.nifi.toolkit.client.ControllerClient;
import org.apache.nifi.toolkit.client.ControllerServicesClient;
import org.apache.nifi.toolkit.client.CountersClient;
@@ -311,6 +312,16 @@ public class JerseyNiFiClient implements NiFiClient {
return new JerseySystemDiagnosticsClient(baseTarget, requestConfig);
}
+ @Override
+ public ConnectorClient getConnectorClient() {
+ return new JerseyConnectorClient(baseTarget);
+ }
+
+ @Override
+ public ConnectorClient getConnectorClient(final RequestConfig requestConfig) {
+ return new JerseyConnectorClient(baseTarget, requestConfig);
+ }
+
@Override
public void close() {
if (this.client != null) {
(nifi) 04/40: NIFI-15258: Addressed PMD findings
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 673db8e21d4873a7487e60add70d79a3ac5ee1c1
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Tue Dec 9 12:36:08 2025 -0500
NIFI-15258: Addressed PMD findings
---
.../server/MockConnectorInitializationContext.java | 6 ++
.../server/StandardConnectorMockServer.java | 2 +-
.../nifi/controller/StandardProcessorNode.java | 1 +
.../apache/nifi/groups/StandardProcessGroup.java | 2 +-
.../components/connector/FrameworkFlowContext.java | 1 +
.../MutableConnectorConfigurationContext.java | 2 +
.../org/apache/nifi/events/BulletinFactory.java | 1 -
.../StandardConnectorInitializationContext.java | 6 ++
.../connector/StandardConnectorNode.java | 21 ++---
.../flow/FlowControllerFlowContextFactory.java | 4 -
.../nifi/controller/flow/StandardFlowManager.java | 1 -
.../connector/StandardConnectorNodeIT.java | 96 ++++++++++------------
.../components/connector/processors/Sleep.java | 2 +-
.../nifi/controller/MockStateManagerProvider.java | 2 +
14 files changed, 74 insertions(+), 73 deletions(-)
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
index 38dcec3474..939035bc5e 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
@@ -123,31 +123,37 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni
this.mockExtensionMapper = mockExtensionMapper;
}
+ @Override
public Builder identifier(final String identifier) {
this.identifier = identifier;
return this;
}
+ @Override
public Builder name(final String name) {
this.name = name;
return this;
}
+ @Override
public Builder componentLog(final ComponentLog componentLog) {
this.componentLog = componentLog;
return this;
}
+ @Override
public Builder secretsManager(final SecretsManager secretsManager) {
this.secretsManager = secretsManager;
return this;
}
+ @Override
public Builder assetManager(final AssetManager assetManager) {
this.assetManager = assetManager;
return this;
}
+ @Override
public MockConnectorInitializationContext build() {
return new MockConnectorInitializationContext(this);
}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
index 1b8034c9ff..54af7b0da6 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
@@ -179,7 +179,7 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
@Override
public void registerMockBundle(final ClassLoader classLoader, final File workingDirectory) {
- final BundleDetails mockBundleDetails = new BundleDetails.Builder()
+ new BundleDetails.Builder()
.workingDir(workingDirectory)
.coordinate(new BundleCoordinate("org.apache.nifi.mock", "nifi-connector-mock-bundle", "1.0.0"))
.build();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
index b7e431aa27..3823428856 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
@@ -1940,6 +1940,7 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
return getConnectorMethods(getProcessor().getClass());
}
+ @Override
public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final ProcessContext processContext) throws InvocationFailedException {
final ConfigurableComponent component = getComponent();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
index 9fbfde6f0a..658ee0cc9d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
@@ -4570,7 +4570,7 @@ public final class StandardProcessGroup implements ProcessGroup {
public CompletableFuture<Void> purge() {
final CompletableFuture<Void> purgeFuture = new CompletableFuture<>();
- final Thread purgeThread = Thread.ofVirtual().name("Purge " + this).start(() -> {
+ Thread.ofVirtual().name("Purge " + this).start(() -> {
try {
stopProcessing().get();
controllerServiceProvider.disableControllerServicesAsync(getControllerServices(true)).get();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkFlowContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkFlowContext.java
index 8880606df0..2c05207bf8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkFlowContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkFlowContext.java
@@ -25,6 +25,7 @@ import org.apache.nifi.groups.ProcessGroup;
public interface FrameworkFlowContext extends FlowContext {
ProcessGroup getManagedProcessGroup();
+ @Override
MutableConnectorConfigurationContext getConfigurationContext();
void updateFlow(VersionedExternalFlow versionedExternalFlow, AssetManager assetManager) throws FlowUpdateException;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
index 2a3e804551..fd428b6882 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
@@ -47,11 +47,13 @@ public interface MutableConnectorConfigurationContext extends ConnectorConfigura
*/
ConnectorConfiguration toConnectorConfiguration();
+ @Override
MutableConnectorConfigurationContext createWithOverrides(String stepName, List<PropertyGroupConfiguration> propertyGroupConfigurations);
/**
* Creates a clone of this MutableConnectorConfigurationContext.
* @return the cloned context
*/
+ @Override
MutableConnectorConfigurationContext clone();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/events/BulletinFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/events/BulletinFactory.java
index b439ef367b..86c80ce986 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/events/BulletinFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/events/BulletinFactory.java
@@ -70,7 +70,6 @@ public final class BulletinFactory {
}
public static Bulletin createBulletin(final ConnectorNode connector, final String category, final String severity, final String message) {
- final String groupId = connector.getProcessGroupIdentifier();
final String connectorName = connector.getName();
return createBulletin(null, connectorName, connector.getIdentifier(), ComponentType.CONNECTOR, connector.getName(), category, severity, message, null, null);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
index 1eb746ee1a..89730a5bac 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
@@ -90,31 +90,37 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto
private SecretsManager secretsManager;
private AssetManager assetManager;
+ @Override
public Builder identifier(final String identifier) {
this.identifier = identifier;
return this;
}
+ @Override
public Builder name(final String name) {
this.name = name;
return this;
}
+ @Override
public Builder componentLog(final ComponentLog componentLog) {
this.componentLog = componentLog;
return this;
}
+ @Override
public Builder secretsManager(final SecretsManager secretsManager) {
this.secretsManager = secretsManager;
return this;
}
+ @Override
public Builder assetManager(final AssetManager assetManager) {
this.assetManager = assetManager;
return this;
}
+ @Override
public StandardConnectorInitializationContext build() {
return new StandardConnectorInitializationContext(this);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 75f7f5828b..d7457d0a59 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -80,7 +80,6 @@ public class StandardConnectorNode implements ConnectorNode {
private volatile FrameworkFlowContext workingFlowContext;
private volatile String name;
- private volatile boolean performValidation = true;
private volatile FrameworkConnectorInitializationContext initializationContext;
@@ -494,7 +493,7 @@ public class StandardConnectorNode implements ConnectorNode {
stepName, groupName, propertyName, this));
}
- try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
return getConnector().fetchAllowableValues(stepName, groupName, propertyName, workingFlowContext);
}
}
@@ -506,7 +505,7 @@ public class StandardConnectorNode implements ConnectorNode {
stepName, groupName, propertyName, this));
}
- try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
return getConnector().fetchAllowableValues(stepName, groupName, propertyName, workingFlowContext, filter);
}
}
@@ -515,7 +514,7 @@ public class StandardConnectorNode implements ConnectorNode {
public void initializeConnector(final FrameworkConnectorInitializationContext initializationContext) {
this.initializationContext = initializationContext;
- try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
getConnector().initialize(initializationContext);
}
@@ -529,7 +528,7 @@ public class StandardConnectorNode implements ConnectorNode {
}
final VersionedExternalFlow initialFlow;
- try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
initialFlow = getConnector().getInitialFlow();
}
@@ -551,13 +550,11 @@ public class StandardConnectorNode implements ConnectorNode {
@Override
public void pauseValidationTrigger() {
- performValidation = false;
+ // No-op for ConnectorNode
}
@Override
public void resumeValidationTrigger() {
- performValidation = true;
-
logger.debug("Resuming Triggering of Validation State for {}; Resetting validation state", this);
resetValidationState();
}
@@ -566,7 +563,7 @@ public class StandardConnectorNode implements ConnectorNode {
public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) {
final List<ConfigVerificationResult> results = new ArrayList<>();
- try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
final DescribedValueProvider allowableValueProvider = (step, groupName, propertyName) ->
fetchAllowableValues(step, groupName, propertyName, workingFlowContext);
@@ -644,7 +641,7 @@ public class StandardConnectorNode implements ConnectorNode {
return results;
}
- try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
results.addAll(getConnector().verify(workingFlowContext));
}
@@ -669,7 +666,7 @@ public class StandardConnectorNode implements ConnectorNode {
@Override
public List<ConfigurationStep> getConfigurationSteps() {
- try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
return getConnector().getConfigurationSteps(workingFlowContext);
}
}
@@ -771,7 +768,7 @@ public class StandardConnectorNode implements ConnectorNode {
private List<DescribedValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext context) {
final List<AllowableValue> allowableValues;
- try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
allowableValues = getConnector().fetchAllowableValues(stepName, groupName, propertyName, activeFlowContext);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
index 651672274d..c488caf03d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
@@ -25,8 +25,6 @@ import org.apache.nifi.components.connector.ProcessGroupFacadeFactory;
import org.apache.nifi.components.connector.ProcessGroupFactory;
import org.apache.nifi.components.connector.StandardFlowContext;
import org.apache.nifi.components.connector.components.FlowContextType;
-import org.apache.nifi.components.connector.components.ParameterContextFacade;
-import org.apache.nifi.components.connector.components.ProcessGroupFacade;
import org.apache.nifi.components.connector.facades.standalone.StandaloneParameterContextFacade;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.flow.Bundle;
@@ -78,8 +76,6 @@ public class FlowControllerFlowContextFactory implements FlowContextFactory {
final ProcessGroup processGroup = processGroupFactory.create(workingGroupId);
copyGroupContents(activeManagedProcessGroup, processGroup, connectorId + "-working-context");
- final ProcessGroupFacade processGroupFacade = processGroupFacadeFactory.create(processGroup, connectorLogger);
- final ParameterContextFacade parameterContextFacade = new StandaloneParameterContextFacade(flowController, processGroup);
final MutableConnectorConfigurationContext workingConfigurationContext = activeConfigurationContext.clone();
return new StandardFlowContext(processGroup, workingConfigurationContext, processGroupFacadeFactory,
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 32be852ce7..d4e5fd6e87 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -753,7 +753,6 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
final ExtensionManager extensionManager = flowController.getExtensionManager();
final String managedGroupId = UUID.nameUUIDFromBytes((id + "-root").getBytes(StandardCharsets.UTF_8)).toString();
- final String workingGroupId = UUID.nameUUIDFromBytes((id + "-working").getBytes(StandardCharsets.UTF_8)).toString();
final ProcessGroup managedRootGroup = createProcessGroup(managedGroupId);
final String paramContextId = UUID.nameUUIDFromBytes((id + "-parameter-context").getBytes(StandardCharsets.UTF_8)).toString();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
index 275b50fcd5..60d2f0dd90 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
@@ -226,13 +226,11 @@ public class StandardConnectorNodeIT {
}
private void configure(final ConnectorNode connectorNode, final ConnectorConfiguration configuration) throws FlowUpdateException {
- try (final FlowEngine flowEngine = new FlowEngine(1, "flow-engine")) {
- connectorNode.prepareForUpdate();
- for (final ConfigurationStepConfiguration stepConfig : configuration.getConfigurationStepConfigurations()) {
- connectorNode.setConfiguration(stepConfig.stepName(), stepConfig.propertyGroupConfigurations());
- }
- connectorNode.applyUpdate();
+ connectorNode.prepareForUpdate();
+ for (final ConfigurationStepConfiguration stepConfig : configuration.getConfigurationStepConfigurations()) {
+ connectorNode.setConfiguration(stepConfig.stepName(), stepConfig.propertyGroupConfigurations());
}
+ connectorNode.applyUpdate();
}
private ConnectorNode initializeDynamicFlowConnector() {
@@ -350,14 +348,12 @@ public class StandardConnectorNodeIT {
// Because the component is being removed and there's data queued in its incoming connection, it should fail.
final ConnectorConfiguration removeLogConfiguration = createConnectorConfiguration("Second Iteration", 5, false, false);
- try (final FlowEngine flowEngine = new FlowEngine(1, "flow-engine")) {
- connectorNode.prepareForUpdate();
- final Throwable cause = assertThrows(FlowUpdateException.class, () -> configure(connectorNode, removeLogConfiguration));
- connectorNode.abortUpdate(cause);
+ connectorNode.prepareForUpdate();
+ final Throwable cause = assertThrows(FlowUpdateException.class, () -> configure(connectorNode, removeLogConfiguration));
+ connectorNode.abortUpdate(cause);
- rootGroup.findAllConnections().contains(connection);
- assertFalse(connection.getFlowFileQueue().isEmpty());
- }
+ rootGroup.findAllConnections().contains(connection);
+ assertFalse(connection.getFlowFileQueue().isEmpty());
}
@@ -367,33 +363,31 @@ public class StandardConnectorNodeIT {
SystemBundle.SYSTEM_BUNDLE_COORDINATE, true, true);
assertNotNull(connectorNode);
- try (final FlowEngine flowEngine = new FlowEngine(1, "flow-engine")) {
- connectorNode.prepareForUpdate();
- assertEquals(List.of("File"), getConfigurationStepNames(connectorNode));
+ connectorNode.prepareForUpdate();
+ assertEquals(List.of("File"), getConfigurationStepNames(connectorNode));
- final Path tempFile = Files.createTempFile("StandardConnectorNodeIT", ".txt");
- Files.writeString(tempFile, String.join("\n", "red", "blue", "yellow"));
+ final Path tempFile = Files.createTempFile("StandardConnectorNodeIT", ".txt");
+ Files.writeString(tempFile, String.join("\n", "red", "blue", "yellow"));
- final ConnectorConfiguration configuration = createFileConfiguration(tempFile.toFile().getAbsolutePath());
- configure(connectorNode, configuration);
+ final ConnectorConfiguration configuration = createFileConfiguration(tempFile.toFile().getAbsolutePath());
+ configure(connectorNode, configuration);
- connectorNode.prepareForUpdate();
- assertEquals(List.of("File", "Colors"), getConfigurationStepNames(connectorNode));
+ connectorNode.prepareForUpdate();
+ assertEquals(List.of("File", "Colors"), getConfigurationStepNames(connectorNode));
- final ConfigurationStep colorConfigurationStep = connectorNode.getConfigurationSteps().stream()
- .filter(step -> step.getName().equals("Colors"))
- .findFirst()
- .orElseThrow();
+ final ConfigurationStep colorConfigurationStep = connectorNode.getConfigurationSteps().stream()
+ .filter(step -> step.getName().equals("Colors"))
+ .findFirst()
+ .orElseThrow();
- assertNotNull(colorConfigurationStep);
- assertEquals(1, colorConfigurationStep.getPropertyGroups().size());
- final ConnectorPropertyGroup primaryColorsPropertyGroup = colorConfigurationStep.getPropertyGroups().getFirst();
+ assertNotNull(colorConfigurationStep);
+ assertEquals(1, colorConfigurationStep.getPropertyGroups().size());
+ final ConnectorPropertyGroup primaryColorsPropertyGroup = colorConfigurationStep.getPropertyGroups().getFirst();
- final List<String> allowableValues = primaryColorsPropertyGroup.getProperties().getFirst().getAllowableValues().stream()
- .map(DescribedValue::getValue)
- .toList();
- assertEquals(List.of("red", "blue", "yellow"), allowableValues);
- }
+ final List<String> allowableValues = primaryColorsPropertyGroup.getProperties().getFirst().getAllowableValues().stream()
+ .map(DescribedValue::getValue)
+ .toList();
+ assertEquals(List.of("red", "blue", "yellow"), allowableValues);
}
@Test
@@ -402,29 +396,27 @@ public class StandardConnectorNodeIT {
SystemBundle.SYSTEM_BUNDLE_COORDINATE, true, true);
assertNotNull(connectorNode);
- try (final FlowEngine flowEngine = new FlowEngine(1, "testSimpleValidation")) {
- connectorNode.prepareForUpdate();
- assertEquals(List.of("File"), getConfigurationStepNames(connectorNode));
+ connectorNode.prepareForUpdate();
+ assertEquals(List.of("File"), getConfigurationStepNames(connectorNode));
- final ConnectorConfiguration configuration = createFileConfiguration("/non/existent/file");
- configure(connectorNode, configuration);
+ final ConnectorConfiguration configuration = createFileConfiguration("/non/existent/file");
+ configure(connectorNode, configuration);
- final ValidationState validationState = connectorNode.performValidation();
- assertNotNull(validationState);
- assertEquals(ValidationStatus.INVALID, validationState.getStatus());
- assertEquals(1, validationState.getValidationErrors().size());
+ final ValidationState validationState = connectorNode.performValidation();
+ assertNotNull(validationState);
+ assertEquals(ValidationStatus.INVALID, validationState.getStatus());
+ assertEquals(1, validationState.getValidationErrors().size());
- final ValidationResult result = validationState.getValidationErrors().iterator().next();
- result.getExplanation().contains("/non/existent/file");
+ final ValidationResult result = validationState.getValidationErrors().iterator().next();
+ result.getExplanation().contains("/non/existent/file");
- final ConnectorConfiguration validConfig = createFileConfiguration(".");
- configure(connectorNode, validConfig);
+ final ConnectorConfiguration validConfig = createFileConfiguration(".");
+ configure(connectorNode, validConfig);
- final ValidationState updatedValidationState = connectorNode.performValidation();
- assertEquals(ValidationStatus.VALID, updatedValidationState.getStatus(),
- "Expected valid state but invalid due to " + updatedValidationState.getValidationErrors());
- assertEquals(List.of(), updatedValidationState.getValidationErrors());
- }
+ final ValidationState updatedValidationState = connectorNode.performValidation();
+ assertEquals(ValidationStatus.VALID, updatedValidationState.getStatus(),
+ "Expected valid state but invalid due to " + updatedValidationState.getValidationErrors());
+ assertEquals(List.of(), updatedValidationState.getValidationErrors());
}
@Test
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/processors/Sleep.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/processors/Sleep.java
index 76e6c51c90..f936ece3f2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/processors/Sleep.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/processors/Sleep.java
@@ -145,7 +145,7 @@ public class Sleep extends AbstractProcessor {
}
} catch (final InterruptedException interruptedException) {
Thread.currentThread().interrupt();
- logger.warn("Sleep processor interrupted while sleeping for {} ms", new Object[] {durationMillis});
+ logger.warn("Sleep processor interrupted while sleeping for {} ms", durationMillis);
}
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/MockStateManagerProvider.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/MockStateManagerProvider.java
index 474b5b5464..e0766c2b3e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/MockStateManagerProvider.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/MockStateManagerProvider.java
@@ -44,6 +44,7 @@ public class MockStateManagerProvider implements StateManagerProvider {
return stateManager;
}
+ @Override
public StateManager getStateManager(final String componentId, final boolean dropStateKeySupported) {
return getStateManager(componentId);
}
@@ -60,6 +61,7 @@ public class MockStateManagerProvider implements StateManagerProvider {
public void disableClusterProvider() {
}
+ @Override
public boolean isClusterProviderEnabled() {
return false;
}
(nifi) 20/40: NIFI-15356: Adding authorization to the StandardNiFiConnectorWebContext. (#10660)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 7417597727e616d5ba89e870c54f96c50c2571e8
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Mon Dec 22 09:01:28 2025 -0500
NIFI-15356: Adding authorization to the StandardNiFiConnectorWebContext. (#10660)
---
.../nifi/web/api/entity/NarDetailsEntity.java | 10 +
.../java/org/apache/nifi/nar/NarInstallTask.java | 4 +-
.../nifi/web/StandardNiFiConnectorWebContext.java | 44 ----
.../apache/nifi/web/StandardNiFiServiceFacade.java | 2 +
.../configuration/WebApplicationConfiguration.java | 12 +-
.../connector/StandardNiFiConnectorWebContext.java | 99 +++++++++
.../authorization/AuthorizingConnectionFacade.java | 55 +++++
.../AuthorizingConnectorInvocationHandler.java | 130 +++++++++++
.../AuthorizingControllerServiceFacade.java | 99 +++++++++
.../AuthorizingControllerServiceLifecycle.java | 56 +++++
.../authorization/AuthorizingFlowContext.java | 70 ++++++
.../AuthorizingParameterContextFacade.java | 72 +++++++
.../AuthorizingProcessGroupFacade.java | 145 +++++++++++++
.../AuthorizingProcessGroupLifecycle.java | 88 ++++++++
.../authorization/AuthorizingProcessorFacade.java | 99 +++++++++
.../AuthorizingProcessorLifecycle.java | 80 +++++++
.../AuthorizingStatelessGroupLifecycle.java | 55 +++++
.../ConnectorAuthorizationContext.java | 74 +++++++
.../StandardNiFiConnectorWebContextTest.java | 219 +++++++++++++++++++
.../AuthorizingConnectorInvocationHandlerTest.java | 239 +++++++++++++++++++++
.../authorization/AuthorizingFlowContextTest.java | 167 ++++++++++++++
.../AuthorizingParameterContextFacadeTest.java | 162 ++++++++++++++
22 files changed, 1935 insertions(+), 46 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/NarDetailsEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/NarDetailsEntity.java
index 799677beee..35c4735e47 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/NarDetailsEntity.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/NarDetailsEntity.java
@@ -36,6 +36,7 @@ public class NarDetailsEntity extends Entity {
private Set<DocumentedTypeDTO> parameterProviderTypes;
private Set<DocumentedTypeDTO> flowRegistryClientTypes;
private Set<DocumentedTypeDTO> flowAnalysisRuleTypes;
+ private Set<DocumentedTypeDTO> connectorTypes;
@Schema(description = "The NAR summary")
public NarSummaryDTO getNarSummary() {
@@ -108,4 +109,13 @@ public class NarDetailsEntity extends Entity {
public void setFlowAnalysisRuleTypes(final Set<DocumentedTypeDTO> flowAnalysisRuleTypes) {
this.flowAnalysisRuleTypes = flowAnalysisRuleTypes;
}
+
+ @Schema(description = "The Connector types contained in the NAR")
+ public Set<DocumentedTypeDTO> getConnectorTypes() {
+ return connectorTypes;
+ }
+
+ public void setConnectorTypes(final Set<DocumentedTypeDTO> connectorTypes) {
+ this.connectorTypes = connectorTypes;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/NarInstallTask.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/NarInstallTask.java
index 8c2673353d..ffdb2fa435 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/NarInstallTask.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/NarInstallTask.java
@@ -20,6 +20,7 @@ package org.apache.nifi.nar;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.service.ControllerServiceProvider;
import org.apache.nifi.flowanalysis.FlowAnalysisRule;
@@ -54,7 +55,8 @@ public class NarInstallTask implements Runnable {
ReportingTask.class,
FlowRegistryClient.class,
FlowAnalysisRule.class,
- ParameterProvider.class
+ ParameterProvider.class,
+ Connector.class
);
private final NarNode narNode;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java
deleted file mode 100644
index 67c011330a..0000000000
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.web;
-
-import org.apache.nifi.components.connector.ConnectorNode;
-import org.apache.nifi.web.dao.ConnectorDAO;
-
-/**
- * Implements the NiFiConnectorWebContext interface to provide
- * Connector instances to connector custom UIs.
- */
-public class StandardNiFiConnectorWebContext implements NiFiConnectorWebContext {
-
- private ConnectorDAO connectorDAO;
-
- @Override
- @SuppressWarnings("unchecked")
- public <T> ConnectorWebContext<T> getConnectorWebContext(final String connectorId) throws IllegalArgumentException {
- final ConnectorNode connectorNode = connectorDAO.getConnector(connectorId);
- if (connectorNode == null) {
- throw new IllegalArgumentException("Unable to find connector with id: " + connectorId);
- }
- return new ConnectorWebContext<>((T) connectorNode.getConnector(), connectorNode.getWorkingFlowContext(), connectorNode.getActiveFlowContext());
- }
-
- public void setConnectorDAO(final ConnectorDAO connectorDAO) {
- this.connectorDAO = connectorDAO;
- }
-}
-
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 9130a348f7..a1472b8c6d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -78,6 +78,7 @@ import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.RequiredPermission;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
+import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.components.connector.Secret;
@@ -7374,6 +7375,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
componentTypesEntity.setParameterProviderTypes(dtoFactory.fromDocumentedTypes(getTypes(extensionDefinitions, ParameterProvider.class)));
componentTypesEntity.setFlowRegistryClientTypes(dtoFactory.fromDocumentedTypes(getTypes(extensionDefinitions, FlowRegistryClient.class)));
componentTypesEntity.setFlowAnalysisRuleTypes(dtoFactory.fromDocumentedTypes(getTypes(extensionDefinitions, FlowAnalysisRule.class)));
+ componentTypesEntity.setConnectorTypes(dtoFactory.fromDocumentedTypes(getTypes(extensionDefinitions, Connector.class)));
return componentTypesEntity;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
index 0659d8ef72..ef53f7b877 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
@@ -18,6 +18,7 @@ package org.apache.nifi.web.configuration;
import org.apache.nifi.admin.service.AuditService;
import org.apache.nifi.audit.NiFiAuditor;
+import org.apache.nifi.authorization.AuthorizableLookup;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.StandardAuthorizableLookup;
import org.apache.nifi.cluster.coordination.ClusterCoordinator;
@@ -35,7 +36,7 @@ import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.NiFiServiceFacadeLock;
import org.apache.nifi.web.NiFiConnectorWebContext;
import org.apache.nifi.web.NiFiWebConfigurationContext;
-import org.apache.nifi.web.StandardNiFiConnectorWebContext;
+import org.apache.nifi.web.connector.StandardNiFiConnectorWebContext;
import org.apache.nifi.web.StandardNiFiContentAccess;
import org.apache.nifi.web.StandardNiFiServiceFacade;
import org.apache.nifi.web.StandardNiFiWebConfigurationContext;
@@ -109,6 +110,8 @@ public class WebApplicationConfiguration {
private ConnectorDAO connectorDAO;
+ private AuthorizableLookup authorizableLookup;
+
public WebApplicationConfiguration(
final Authorizer authorizer,
final AccessPolicyDAO accessPolicyDao,
@@ -155,6 +158,11 @@ public class WebApplicationConfiguration {
this.connectorDAO = connectorDAO;
}
+ @Autowired
+ public void setAuthorizableLookup(final AuthorizableLookup authorizableLookup) {
+ this.authorizableLookup = authorizableLookup;
+ }
+
@Bean
public EntityFactory entityFactory() {
return new EntityFactory();
@@ -270,6 +278,8 @@ public class WebApplicationConfiguration {
public NiFiConnectorWebContext nifiConnectorWebContext() {
final StandardNiFiConnectorWebContext context = new StandardNiFiConnectorWebContext();
context.setConnectorDAO(connectorDAO);
+ context.setAuthorizer(authorizer);
+ context.setAuthorizableLookup(authorizableLookup);
return context;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContext.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContext.java
new file mode 100644
index 0000000000..5b5018174c
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContext.java
@@ -0,0 +1,99 @@
+/*
+ * 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.web.connector;
+
+import org.apache.nifi.authorization.AuthorizableLookup;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.web.NiFiConnectorWebContext;
+import org.apache.nifi.web.connector.authorization.AuthorizingConnectorInvocationHandler;
+import org.apache.nifi.web.connector.authorization.AuthorizingFlowContext;
+import org.apache.nifi.web.connector.authorization.ConnectorAuthorizationContext;
+import org.apache.nifi.web.dao.ConnectorDAO;
+
+import java.lang.reflect.Proxy;
+
+/**
+ * Implements the NiFiConnectorWebContext interface to provide
+ * Connector instances to connector custom UIs.
+ *
+ * <p>The returned Connector instance is wrapped in an authorization proxy that
+ * enforces permissions based on the {@link ConnectorWebMethod} annotation on
+ * the connector interface methods. Methods without this annotation cannot be
+ * invoked through the proxy.</p>
+ *
+ * <p>The returned FlowContext instances are also wrapped in authorization wrappers
+ * that enforce read/write permissions on all operations.</p>
+ */
+public class StandardNiFiConnectorWebContext implements NiFiConnectorWebContext {
+
+ private ConnectorDAO connectorDAO;
+ private Authorizer authorizer;
+ private AuthorizableLookup authorizableLookup;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> ConnectorWebContext<T> getConnectorWebContext(final String connectorId) throws IllegalArgumentException {
+ final ConnectorNode connectorNode = connectorDAO.getConnector(connectorId);
+ if (connectorNode == null) {
+ throw new IllegalArgumentException("Unable to find connector with id: " + connectorId);
+ }
+
+ final ConnectorAuthorizationContext authContext = new ConnectorAuthorizationContext(connectorId, authorizer, authorizableLookup);
+
+ final T connector = (T) connectorNode.getConnector();
+ final T authorizedConnectorProxy = createAuthorizingProxy(connector, connectorId);
+
+ final FlowContext workingFlowContext = new AuthorizingFlowContext(connectorNode.getWorkingFlowContext(), authContext);
+ final FlowContext activeFlowContext = new AuthorizingFlowContext(connectorNode.getActiveFlowContext(), authContext);
+
+ return new ConnectorWebContext<>(authorizedConnectorProxy, workingFlowContext, activeFlowContext);
+ }
+
+ /**
+ * Creates a proxy around the given connector that enforces authorization
+ * based on {@link ConnectorWebMethod} annotations.
+ *
+ * @param <T> the type of the connector
+ * @param connector the connector instance to wrap
+ * @param connectorId the ID of the connector
+ * @return a proxy that enforces authorization on method invocations
+ */
+ @SuppressWarnings("unchecked")
+ private <T> T createAuthorizingProxy(final T connector, final String connectorId) {
+ final AuthorizingConnectorInvocationHandler<T> handler = new AuthorizingConnectorInvocationHandler<>(
+ connector, connectorId, authorizer, authorizableLookup);
+
+ return (T) Proxy.newProxyInstance(
+ connector.getClass().getClassLoader(),
+ connector.getClass().getInterfaces(),
+ handler);
+ }
+
+ public void setConnectorDAO(final ConnectorDAO connectorDAO) {
+ this.connectorDAO = connectorDAO;
+ }
+
+ public void setAuthorizer(final Authorizer authorizer) {
+ this.authorizer = authorizer;
+ }
+
+ public void setAuthorizableLookup(final AuthorizableLookup authorizableLookup) {
+ this.authorizableLookup = authorizableLookup;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectionFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectionFacade.java
new file mode 100644
index 0000000000..f96642e2b7
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectionFacade.java
@@ -0,0 +1,55 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.connector.components.ConnectionFacade;
+import org.apache.nifi.controller.queue.QueueSize;
+import org.apache.nifi.flow.VersionedConnection;
+
+/**
+ * A wrapper around {@link ConnectionFacade} that enforces authorization before delegating
+ * to the underlying implementation.
+ */
+public class AuthorizingConnectionFacade implements ConnectionFacade {
+
+ private final ConnectionFacade delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingConnectionFacade(final ConnectionFacade delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public VersionedConnection getDefinition() {
+ authContext.authorizeRead();
+ return delegate.getDefinition();
+ }
+
+ @Override
+ public QueueSize getQueueSize() {
+ authContext.authorizeRead();
+ return delegate.getQueueSize();
+ }
+
+ @Override
+ public void purge() {
+ authContext.authorizeWrite();
+ delegate.purge();
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectorInvocationHandler.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectorInvocationHandler.java
new file mode 100644
index 0000000000..130fa43248
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectorInvocationHandler.java
@@ -0,0 +1,130 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.authorization.AuthorizableLookup;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.web.ConnectorWebMethod;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * An InvocationHandler that wraps a Connector instance and enforces authorization
+ * based on the {@link ConnectorWebMethod} annotation present on the invoked method.
+ *
+ * <p>Methods must be annotated with {@link ConnectorWebMethod} to be invokable through
+ * this handler. The annotation specifies whether READ or WRITE access is required.
+ * Methods without the annotation will result in an {@link IllegalStateException}.</p>
+ *
+ * @param <T> the type of the Connector being proxied
+ */
+public class AuthorizingConnectorInvocationHandler<T> implements InvocationHandler {
+
+ private final T delegate;
+ private final String connectorId;
+ private final Authorizer authorizer;
+ private final AuthorizableLookup authorizableLookup;
+
+ /**
+ * Constructs an AuthorizingConnectorInvocationHandler.
+ *
+ * @param delegate the actual Connector instance to delegate method calls to
+ * @param connectorId the ID of the connector, used for authorization lookups
+ * @param authorizer the Authorizer to use for authorization checks
+ * @param authorizableLookup the lookup service to obtain the Authorizable for the connector
+ */
+ public AuthorizingConnectorInvocationHandler(final T delegate, final String connectorId,
+ final Authorizer authorizer, final AuthorizableLookup authorizableLookup) {
+ this.delegate = delegate;
+ this.connectorId = connectorId;
+ this.authorizer = authorizer;
+ this.authorizableLookup = authorizableLookup;
+ }
+
+ @Override
+ public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+ final ConnectorWebMethod annotation = findConnectorWebMethodAnnotation(method);
+
+ if (annotation == null) {
+ throw new IllegalStateException(String.format(
+ "Method [%s] on connector [%s] is not annotated with @ConnectorWebMethod and cannot be invoked through the Connector Web Context",
+ method.getName(), connectorId));
+ }
+
+ final RequestAction requiredAction = mapAccessTypeToRequestAction(annotation.value());
+ final Authorizable connector = authorizableLookup.getConnector(connectorId);
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ connector.authorize(authorizer, requiredAction, user);
+
+ try {
+ return method.invoke(delegate, args);
+ } catch (final InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+
+ /**
+ * Maps the ConnectorWebMethod.AccessType to the corresponding RequestAction.
+ *
+ * @param accessType the access type from the annotation
+ * @return the corresponding RequestAction
+ */
+ private RequestAction mapAccessTypeToRequestAction(final ConnectorWebMethod.AccessType accessType) {
+ return switch (accessType) {
+ case READ -> RequestAction.READ;
+ case WRITE -> RequestAction.WRITE;
+ };
+ }
+
+ /**
+ * Finds the ConnectorWebMethod annotation on the given method. This method searches
+ * the declaring class's interfaces to find the annotation, as the method parameter
+ * may be from the proxy class rather than the interface.
+ *
+ * @param method the method to search for the annotation
+ * @return the ConnectorWebMethod annotation, or null if not found
+ */
+ private ConnectorWebMethod findConnectorWebMethodAnnotation(final Method method) {
+ final ConnectorWebMethod directAnnotation = method.getAnnotation(ConnectorWebMethod.class);
+ if (directAnnotation != null) {
+ return directAnnotation;
+ }
+
+ for (final Class<?> iface : delegate.getClass().getInterfaces()) {
+ try {
+ final Method interfaceMethod = iface.getMethod(method.getName(), method.getParameterTypes());
+ final ConnectorWebMethod interfaceAnnotation = interfaceMethod.getAnnotation(ConnectorWebMethod.class);
+ if (interfaceAnnotation != null) {
+ return interfaceAnnotation;
+ }
+ } catch (final NoSuchMethodException ignored) {
+ // Method not found on this interface; continue searching other interfaces
+ continue;
+ }
+ }
+
+ return null;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceFacade.java
new file mode 100644
index 0000000000..2086c875d4
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceFacade.java
@@ -0,0 +1,99 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.InvocationFailedException;
+import org.apache.nifi.components.connector.components.ControllerServiceFacade;
+import org.apache.nifi.components.connector.components.ControllerServiceLifecycle;
+import org.apache.nifi.flow.VersionedControllerService;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedParameterContext;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper around {@link ControllerServiceFacade} that enforces authorization before delegating
+ * to the underlying implementation.
+ */
+public class AuthorizingControllerServiceFacade implements ControllerServiceFacade {
+
+ private final ControllerServiceFacade delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingControllerServiceFacade(final ControllerServiceFacade delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public VersionedControllerService getDefinition() {
+ authContext.authorizeRead();
+ return delegate.getDefinition();
+ }
+
+ @Override
+ public ControllerServiceLifecycle getLifecycle() {
+ authContext.authorizeRead();
+ return new AuthorizingControllerServiceLifecycle(delegate.getLifecycle(), authContext);
+ }
+
+ @Override
+ public List<ValidationResult> validate() {
+ authContext.authorizeRead();
+ return delegate.validate();
+ }
+
+ @Override
+ public List<ValidationResult> validate(final Map<String, String> propertyValues) {
+ authContext.authorizeRead();
+ return delegate.validate(propertyValues);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final Map<String, String> propertyValues, final Map<String, String> variables) {
+ authContext.authorizeRead();
+ return delegate.verify(propertyValues, variables);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final Map<String, String> propertyValues, final VersionedParameterContext parameterContext, final Map<String, String> variables) {
+ authContext.authorizeRead();
+ return delegate.verify(propertyValues, parameterContext, variables);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final VersionedExternalFlow versionedExternalFlow, final Map<String, String> variables) {
+ authContext.authorizeRead();
+ return delegate.verify(versionedExternalFlow, variables);
+ }
+
+ @Override
+ public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments) throws InvocationFailedException {
+ authContext.authorizeWrite();
+ return delegate.invokeConnectorMethod(methodName, arguments);
+ }
+
+ @Override
+ public <T> T invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final Class<T> returnType) throws InvocationFailedException {
+ authContext.authorizeWrite();
+ return delegate.invokeConnectorMethod(methodName, arguments, returnType);
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceLifecycle.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceLifecycle.java
new file mode 100644
index 0000000000..838632fdfb
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceLifecycle.java
@@ -0,0 +1,56 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.connector.components.ControllerServiceLifecycle;
+import org.apache.nifi.components.connector.components.ControllerServiceState;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A wrapper around {@link ControllerServiceLifecycle} that enforces authorization before delegating
+ * to the underlying implementation.
+ */
+public class AuthorizingControllerServiceLifecycle implements ControllerServiceLifecycle {
+
+ private final ControllerServiceLifecycle delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingControllerServiceLifecycle(final ControllerServiceLifecycle delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public ControllerServiceState getState() {
+ authContext.authorizeRead();
+ return delegate.getState();
+ }
+
+ @Override
+ public CompletableFuture<Void> enable() {
+ authContext.authorizeWrite();
+ return delegate.enable();
+ }
+
+ @Override
+ public CompletableFuture<Void> disable() {
+ authContext.authorizeWrite();
+ return delegate.disable();
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingFlowContext.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingFlowContext.java
new file mode 100644
index 0000000000..d03b5e71fb
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingFlowContext.java
@@ -0,0 +1,70 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.connector.ConnectorConfigurationContext;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.components.connector.components.FlowContextType;
+import org.apache.nifi.components.connector.components.ParameterContextFacade;
+import org.apache.nifi.components.connector.components.ProcessGroupFacade;
+import org.apache.nifi.flow.Bundle;
+
+/**
+ * A wrapper around {@link FlowContext} that enforces authorization before delegating
+ * to the underlying implementation.
+ */
+public class AuthorizingFlowContext implements FlowContext {
+
+ private final FlowContext delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingFlowContext(final FlowContext delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public ProcessGroupFacade getRootGroup() {
+ authContext.authorizeRead();
+ return new AuthorizingProcessGroupFacade(delegate.getRootGroup(), authContext);
+ }
+
+ @Override
+ public ParameterContextFacade getParameterContext() {
+ authContext.authorizeRead();
+ return new AuthorizingParameterContextFacade(delegate.getParameterContext(), authContext);
+ }
+
+ @Override
+ public ConnectorConfigurationContext getConfigurationContext() {
+ authContext.authorizeRead();
+ return delegate.getConfigurationContext();
+ }
+
+ @Override
+ public FlowContextType getType() {
+ authContext.authorizeRead();
+ return delegate.getType();
+ }
+
+ @Override
+ public Bundle getBundle() {
+ authContext.authorizeRead();
+ return delegate.getBundle();
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingParameterContextFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingParameterContextFacade.java
new file mode 100644
index 0000000000..ead31b9481
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingParameterContextFacade.java
@@ -0,0 +1,72 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.asset.Asset;
+import org.apache.nifi.components.connector.components.ParameterContextFacade;
+import org.apache.nifi.components.connector.components.ParameterValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * A wrapper around {@link ParameterContextFacade} that enforces authorization before delegating
+ * to the underlying implementation.
+ */
+public class AuthorizingParameterContextFacade implements ParameterContextFacade {
+
+ private final ParameterContextFacade delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingParameterContextFacade(final ParameterContextFacade delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public void updateParameters(final Collection<ParameterValue> parameterValues) {
+ authContext.authorizeWrite();
+ delegate.updateParameters(parameterValues);
+ }
+
+ @Override
+ public String getValue(final String parameterName) {
+ authContext.authorizeRead();
+ return delegate.getValue(parameterName);
+ }
+
+ @Override
+ public Set<String> getDefinedParameterNames() {
+ authContext.authorizeRead();
+ return delegate.getDefinedParameterNames();
+ }
+
+ @Override
+ public boolean isSensitive(final String parameterName) {
+ authContext.authorizeRead();
+ return delegate.isSensitive(parameterName);
+ }
+
+ @Override
+ public Asset createAsset(final InputStream inputStream) throws IOException {
+ authContext.authorizeWrite();
+ return delegate.createAsset(inputStream);
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupFacade.java
new file mode 100644
index 0000000000..21ade9740d
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupFacade.java
@@ -0,0 +1,145 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.connector.components.ConnectionFacade;
+import org.apache.nifi.components.connector.components.ControllerServiceFacade;
+import org.apache.nifi.components.connector.components.ControllerServiceReferenceHierarchy;
+import org.apache.nifi.components.connector.components.ControllerServiceReferenceScope;
+import org.apache.nifi.components.connector.components.ProcessGroupFacade;
+import org.apache.nifi.components.connector.components.ProcessGroupLifecycle;
+import org.apache.nifi.components.connector.components.ProcessorFacade;
+import org.apache.nifi.components.connector.components.StatelessGroupLifecycle;
+import org.apache.nifi.controller.queue.QueueSize;
+import org.apache.nifi.flow.VersionedProcessGroup;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A wrapper around {@link ProcessGroupFacade} that enforces authorization before delegating
+ * to the underlying implementation.
+ */
+public class AuthorizingProcessGroupFacade implements ProcessGroupFacade {
+
+ private final ProcessGroupFacade delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingProcessGroupFacade(final ProcessGroupFacade delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public VersionedProcessGroup getDefinition() {
+ authContext.authorizeRead();
+ return delegate.getDefinition();
+ }
+
+ @Override
+ public ProcessorFacade getProcessor(final String id) {
+ authContext.authorizeRead();
+ final ProcessorFacade processor = delegate.getProcessor(id);
+ return processor == null ? null : new AuthorizingProcessorFacade(processor, authContext);
+ }
+
+ @Override
+ public Set<ProcessorFacade> getProcessors() {
+ authContext.authorizeRead();
+ return delegate.getProcessors().stream()
+ .map(p -> new AuthorizingProcessorFacade(p, authContext))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public ControllerServiceFacade getControllerService(final String id) {
+ authContext.authorizeRead();
+ final ControllerServiceFacade service = delegate.getControllerService(id);
+ return service == null ? null : new AuthorizingControllerServiceFacade(service, authContext);
+ }
+
+ @Override
+ public Set<ControllerServiceFacade> getControllerServices() {
+ authContext.authorizeRead();
+ return delegate.getControllerServices().stream()
+ .map(s -> new AuthorizingControllerServiceFacade(s, authContext))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<ControllerServiceFacade> getControllerServices(final ControllerServiceReferenceScope referenceScope, final ControllerServiceReferenceHierarchy hierarchy) {
+ authContext.authorizeRead();
+ return delegate.getControllerServices(referenceScope, hierarchy).stream()
+ .map(s -> new AuthorizingControllerServiceFacade(s, authContext))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public ConnectionFacade getConnection(final String id) {
+ authContext.authorizeRead();
+ final ConnectionFacade connection = delegate.getConnection(id);
+ return connection == null ? null : new AuthorizingConnectionFacade(connection, authContext);
+ }
+
+ @Override
+ public Set<ConnectionFacade> getConnections() {
+ authContext.authorizeRead();
+ return delegate.getConnections().stream()
+ .map(c -> new AuthorizingConnectionFacade(c, authContext))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public ProcessGroupFacade getProcessGroup(final String id) {
+ authContext.authorizeRead();
+ final ProcessGroupFacade group = delegate.getProcessGroup(id);
+ return group == null ? null : new AuthorizingProcessGroupFacade(group, authContext);
+ }
+
+ @Override
+ public Set<ProcessGroupFacade> getProcessGroups() {
+ authContext.authorizeRead();
+ return delegate.getProcessGroups().stream()
+ .map(g -> new AuthorizingProcessGroupFacade(g, authContext))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public QueueSize getQueueSize() {
+ authContext.authorizeRead();
+ return delegate.getQueueSize();
+ }
+
+ @Override
+ public boolean isFlowEmpty() {
+ authContext.authorizeRead();
+ return delegate.isFlowEmpty();
+ }
+
+ @Override
+ public StatelessGroupLifecycle getStatelessLifecycle() {
+ authContext.authorizeRead();
+ return new AuthorizingStatelessGroupLifecycle(delegate.getStatelessLifecycle(), authContext);
+ }
+
+ @Override
+ public ProcessGroupLifecycle getLifecycle() {
+ authContext.authorizeRead();
+ return new AuthorizingProcessGroupLifecycle(delegate.getLifecycle(), authContext);
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupLifecycle.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupLifecycle.java
new file mode 100644
index 0000000000..d29a056425
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupLifecycle.java
@@ -0,0 +1,88 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.connector.components.ControllerServiceReferenceHierarchy;
+import org.apache.nifi.components.connector.components.ControllerServiceReferenceScope;
+import org.apache.nifi.components.connector.components.ProcessGroupLifecycle;
+
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A wrapper around {@link ProcessGroupLifecycle} that enforces authorization before delegating
+ * to the underlying implementation. All lifecycle operations require WRITE authorization.
+ */
+public class AuthorizingProcessGroupLifecycle implements ProcessGroupLifecycle {
+
+ private final ProcessGroupLifecycle delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingProcessGroupLifecycle(final ProcessGroupLifecycle delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public CompletableFuture<Void> enableControllerServices(final ControllerServiceReferenceScope scope, final ControllerServiceReferenceHierarchy hierarchy) {
+ authContext.authorizeWrite();
+ return delegate.enableControllerServices(scope, hierarchy);
+ }
+
+ @Override
+ public CompletableFuture<Void> enableControllerServices(final Collection<String> serviceIdentifiers) {
+ authContext.authorizeWrite();
+ return delegate.enableControllerServices(serviceIdentifiers);
+ }
+
+ @Override
+ public CompletableFuture<Void> disableControllerServices(final ControllerServiceReferenceHierarchy hierarchy) {
+ authContext.authorizeWrite();
+ return delegate.disableControllerServices(hierarchy);
+ }
+
+ @Override
+ public CompletableFuture<Void> disableControllerServices(final Collection<String> serviceIdentifiers) {
+ authContext.authorizeWrite();
+ return delegate.disableControllerServices(serviceIdentifiers);
+ }
+
+ @Override
+ public CompletableFuture<Void> startProcessors() {
+ authContext.authorizeWrite();
+ return delegate.startProcessors();
+ }
+
+ @Override
+ public CompletableFuture<Void> start(final ControllerServiceReferenceScope serviceReferenceScope) {
+ authContext.authorizeWrite();
+ return delegate.start(serviceReferenceScope);
+ }
+
+ @Override
+ public CompletableFuture<Void> stop() {
+ authContext.authorizeWrite();
+ return delegate.stop();
+ }
+
+ @Override
+ public CompletableFuture<Void> stopProcessors() {
+ authContext.authorizeWrite();
+ return delegate.stopProcessors();
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessorFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessorFacade.java
new file mode 100644
index 0000000000..22fe562230
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessorFacade.java
@@ -0,0 +1,99 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.InvocationFailedException;
+import org.apache.nifi.components.connector.components.ProcessorFacade;
+import org.apache.nifi.components.connector.components.ProcessorLifecycle;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedParameterContext;
+import org.apache.nifi.flow.VersionedProcessor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper around {@link ProcessorFacade} that enforces authorization before delegating
+ * to the underlying implementation.
+ */
+public class AuthorizingProcessorFacade implements ProcessorFacade {
+
+ private final ProcessorFacade delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingProcessorFacade(final ProcessorFacade delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public VersionedProcessor getDefinition() {
+ authContext.authorizeRead();
+ return delegate.getDefinition();
+ }
+
+ @Override
+ public ProcessorLifecycle getLifecycle() {
+ authContext.authorizeRead();
+ return new AuthorizingProcessorLifecycle(delegate.getLifecycle(), authContext);
+ }
+
+ @Override
+ public List<ValidationResult> validate() {
+ authContext.authorizeRead();
+ return delegate.validate();
+ }
+
+ @Override
+ public List<ValidationResult> validate(final Map<String, String> propertyValues) {
+ authContext.authorizeRead();
+ return delegate.validate(propertyValues);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final Map<String, String> propertyValues, final Map<String, String> attributes) {
+ authContext.authorizeRead();
+ return delegate.verify(propertyValues, attributes);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final Map<String, String> propertyValues, final VersionedParameterContext parameterContext, final Map<String, String> attributes) {
+ authContext.authorizeRead();
+ return delegate.verify(propertyValues, parameterContext, attributes);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final VersionedExternalFlow versionedExternalFlow, final Map<String, String> attributes) {
+ authContext.authorizeRead();
+ return delegate.verify(versionedExternalFlow, attributes);
+ }
+
+ @Override
+ public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments) throws InvocationFailedException {
+ authContext.authorizeWrite();
+ return delegate.invokeConnectorMethod(methodName, arguments);
+ }
+
+ @Override
+ public <T> T invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final Class<T> returnType) throws InvocationFailedException {
+ authContext.authorizeWrite();
+ return delegate.invokeConnectorMethod(methodName, arguments, returnType);
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessorLifecycle.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessorLifecycle.java
new file mode 100644
index 0000000000..562551def8
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessorLifecycle.java
@@ -0,0 +1,80 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.connector.components.ProcessorLifecycle;
+import org.apache.nifi.components.connector.components.ProcessorState;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A wrapper around {@link ProcessorLifecycle} that enforces authorization before delegating
+ * to the underlying implementation.
+ */
+public class AuthorizingProcessorLifecycle implements ProcessorLifecycle {
+
+ private final ProcessorLifecycle delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingProcessorLifecycle(final ProcessorLifecycle delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public ProcessorState getState() {
+ authContext.authorizeRead();
+ return delegate.getState();
+ }
+
+ @Override
+ public int getActiveThreadCount() {
+ authContext.authorizeRead();
+ return delegate.getActiveThreadCount();
+ }
+
+ @Override
+ public void terminate() {
+ authContext.authorizeWrite();
+ delegate.terminate();
+ }
+
+ @Override
+ public CompletableFuture<Void> stop() {
+ authContext.authorizeWrite();
+ return delegate.stop();
+ }
+
+ @Override
+ public CompletableFuture<Void> start() {
+ authContext.authorizeWrite();
+ return delegate.start();
+ }
+
+ @Override
+ public void disable() {
+ authContext.authorizeWrite();
+ delegate.disable();
+ }
+
+ @Override
+ public void enable() {
+ authContext.authorizeWrite();
+ delegate.enable();
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingStatelessGroupLifecycle.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingStatelessGroupLifecycle.java
new file mode 100644
index 0000000000..bf822669a6
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingStatelessGroupLifecycle.java
@@ -0,0 +1,55 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.components.connector.components.StatelessGroupLifecycle;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A wrapper around {@link StatelessGroupLifecycle} that enforces authorization before delegating
+ * to the underlying implementation. All lifecycle operations require WRITE authorization.
+ */
+public class AuthorizingStatelessGroupLifecycle implements StatelessGroupLifecycle {
+
+ private final StatelessGroupLifecycle delegate;
+ private final ConnectorAuthorizationContext authContext;
+
+ public AuthorizingStatelessGroupLifecycle(final StatelessGroupLifecycle delegate, final ConnectorAuthorizationContext authContext) {
+ this.delegate = delegate;
+ this.authContext = authContext;
+ }
+
+ @Override
+ public CompletableFuture<Void> start() {
+ authContext.authorizeWrite();
+ return delegate.start();
+ }
+
+ @Override
+ public CompletableFuture<Void> stop() {
+ authContext.authorizeWrite();
+ return delegate.stop();
+ }
+
+ @Override
+ public CompletableFuture<Void> terminate() {
+ authContext.authorizeWrite();
+ return delegate.terminate();
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/ConnectorAuthorizationContext.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/ConnectorAuthorizationContext.java
new file mode 100644
index 0000000000..00f9930047
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/ConnectorAuthorizationContext.java
@@ -0,0 +1,74 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.authorization.AuthorizableLookup;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+
+/**
+ * Holds the authorization context needed to authorize operations on a Connector's FlowContext
+ * and its associated facades.
+ */
+public class ConnectorAuthorizationContext {
+
+ private final String connectorId;
+ private final Authorizer authorizer;
+ private final AuthorizableLookup authorizableLookup;
+
+ public ConnectorAuthorizationContext(final String connectorId, final Authorizer authorizer, final AuthorizableLookup authorizableLookup) {
+ this.connectorId = connectorId;
+ this.authorizer = authorizer;
+ this.authorizableLookup = authorizableLookup;
+ }
+
+ /**
+ * Authorizes the current user for read access to the connector.
+ */
+ public void authorizeRead() {
+ authorize(RequestAction.READ);
+ }
+
+ /**
+ * Authorizes the current user for write access to the connector.
+ */
+ public void authorizeWrite() {
+ authorize(RequestAction.WRITE);
+ }
+
+ private void authorize(final RequestAction action) {
+ final Authorizable connector = authorizableLookup.getConnector(connectorId);
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ connector.authorize(authorizer, action, user);
+ }
+
+ public String getConnectorId() {
+ return connectorId;
+ }
+
+ public Authorizer getAuthorizer() {
+ return authorizer;
+ }
+
+ public AuthorizableLookup getAuthorizableLookup() {
+ return authorizableLookup;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContextTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContextTest.java
new file mode 100644
index 0000000000..321e7ff02c
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContextTest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.web.connector;
+
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.components.connector.Connector;
+import org.apache.nifi.web.ConnectorWebMethod;
+import org.apache.nifi.web.ConnectorWebMethod.AccessType;
+import org.apache.nifi.authorization.AuthorizableLookup;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.web.NiFiConnectorWebContext.ConnectorWebContext;
+import org.apache.nifi.web.connector.authorization.AuthorizingFlowContext;
+import org.apache.nifi.web.dao.ConnectorDAO;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.lang.reflect.Proxy;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+@ExtendWith(MockitoExtension.class)
+public class StandardNiFiConnectorWebContextTest {
+
+ private static final String CONNECTOR_ID = "test-connector-id";
+ private static final String USER_IDENTITY = "test-user";
+
+ @Mock
+ private ConnectorDAO connectorDAO;
+
+ @Mock
+ private Authorizer authorizer;
+
+ @Mock
+ private AuthorizableLookup authorizableLookup;
+
+ @Mock
+ private ConnectorNode connectorNode;
+
+ @Mock
+ private FrameworkFlowContext workingFlowContext;
+
+ @Mock
+ private FrameworkFlowContext activeFlowContext;
+
+ @Mock
+ private Authorizable connectorAuthorizable;
+
+ @Mock
+ private SecurityContext securityContext;
+
+ @Mock
+ private Authentication authentication;
+
+ private StandardNiFiConnectorWebContext context;
+
+ private TestConnector testConnectorMock;
+ private String lastWrittenValue;
+
+ @BeforeEach
+ void setUp() {
+ context = new StandardNiFiConnectorWebContext();
+ context.setConnectorDAO(connectorDAO);
+ context.setAuthorizer(authorizer);
+ context.setAuthorizableLookup(authorizableLookup);
+
+ testConnectorMock = mock(TestConnector.class, withSettings().extraInterfaces(Connector.class).lenient());
+ lenient().when(testConnectorMock.readData()).thenReturn("read-result");
+ lenient().when(testConnectorMock.writeData(any())).thenAnswer(invocation -> {
+ lastWrittenValue = invocation.getArgument(0);
+ return null;
+ });
+
+ lenient().when(connectorDAO.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+ lenient().when(connectorNode.getConnector()).thenReturn((Connector) testConnectorMock);
+ lenient().when(connectorNode.getWorkingFlowContext()).thenReturn(workingFlowContext);
+ lenient().when(connectorNode.getActiveFlowContext()).thenReturn(activeFlowContext);
+ lenient().when(authorizableLookup.getConnector(CONNECTOR_ID)).thenReturn(connectorAuthorizable);
+ lenient().when(authorizer.authorize(any(AuthorizationRequest.class))).thenReturn(AuthorizationResult.approved());
+
+ final NiFiUser user = new StandardNiFiUser.Builder().identity(USER_IDENTITY).build();
+ final NiFiUserDetails userDetails = new NiFiUserDetails(user);
+ lenient().when(authentication.getPrincipal()).thenReturn(userDetails);
+ lenient().when(securityContext.getAuthentication()).thenReturn(authentication);
+ SecurityContextHolder.setContext(securityContext);
+ }
+
+ @AfterEach
+ void tearDown() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ void testGetConnectorWebContextReturnsProxiedConnector() {
+ final ConnectorWebContext<TestConnector> webContext = context.getConnectorWebContext(CONNECTOR_ID);
+
+ assertNotNull(webContext);
+ assertNotNull(webContext.connector());
+ assertTrue(Proxy.isProxyClass(webContext.connector().getClass()));
+ assertNotSame(testConnectorMock, webContext.connector());
+ }
+
+ @Test
+ void testGetConnectorWebContextReturnsWrappedFlowContexts() {
+ final ConnectorWebContext<TestConnector> webContext = context.getConnectorWebContext(CONNECTOR_ID);
+
+ assertNotNull(webContext.workingFlowContext());
+ assertNotNull(webContext.activeFlowContext());
+ assertTrue(webContext.workingFlowContext() instanceof AuthorizingFlowContext);
+ assertTrue(webContext.activeFlowContext() instanceof AuthorizingFlowContext);
+ }
+
+ @Test
+ void testGetConnectorWebContextThrowsForNonExistentConnector() {
+ when(connectorDAO.getConnector("non-existent-id")).thenReturn(null);
+
+ final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+ () -> context.getConnectorWebContext("non-existent-id"));
+ assertEquals("Unable to find connector with id: non-existent-id", exception.getMessage());
+ }
+
+ @Test
+ void testProxiedConnectorEnforcesReadAuthorization() {
+ final ConnectorWebContext<TestConnector> webContext = context.getConnectorWebContext(CONNECTOR_ID);
+ final TestConnector proxy = webContext.connector();
+
+ final String result = proxy.readData();
+
+ assertEquals("read-result", result);
+ verify(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+ }
+
+ @Test
+ void testProxiedConnectorEnforcesWriteAuthorization() {
+ final ConnectorWebContext<TestConnector> webContext = context.getConnectorWebContext(CONNECTOR_ID);
+ final TestConnector proxy = webContext.connector();
+
+ proxy.writeData("test-value");
+
+ assertEquals("test-value", lastWrittenValue);
+ verify(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+ }
+
+ @Test
+ void testProxiedConnectorBlocksUnannotatedMethods() {
+ final ConnectorWebContext<TestConnector> webContext = context.getConnectorWebContext(CONNECTOR_ID);
+ final TestConnector proxy = webContext.connector();
+
+ assertThrows(IllegalStateException.class, proxy::unannotatedMethod);
+ }
+
+ @Test
+ void testProxiedConnectorPropagatesAuthorizationFailure() {
+ doThrow(new AccessDeniedException("Access denied"))
+ .when(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+
+ final ConnectorWebContext<TestConnector> webContext = context.getConnectorWebContext(CONNECTOR_ID);
+ final TestConnector proxy = webContext.connector();
+
+ assertThrows(AccessDeniedException.class, proxy::readData);
+ }
+
+ /**
+ * Test interface representing a Connector with annotated methods.
+ * This interface is used with Mockito's extraInterfaces to create a mock
+ * that implements both this interface and Connector.
+ */
+ public interface TestConnector {
+
+ @ConnectorWebMethod(AccessType.READ)
+ String readData();
+
+ @ConnectorWebMethod(AccessType.WRITE)
+ Void writeData(String value);
+
+ void unannotatedMethod();
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectorInvocationHandlerTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectorInvocationHandlerTest.java
new file mode 100644
index 0000000000..e12b7366f9
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingConnectorInvocationHandlerTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.AuthorizableLookup;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.web.ConnectorWebMethod;
+import org.apache.nifi.web.ConnectorWebMethod.AccessType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.lang.reflect.Proxy;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+public class AuthorizingConnectorInvocationHandlerTest {
+
+ private static final String CONNECTOR_ID = "test-connector-id";
+ private static final String USER_IDENTITY = "test-user";
+
+ @Mock
+ private Authorizer authorizer;
+
+ @Mock
+ private AuthorizableLookup authorizableLookup;
+
+ @Mock
+ private Authorizable connectorAuthorizable;
+
+ @Mock
+ private SecurityContext securityContext;
+
+ @Mock
+ private Authentication authentication;
+
+ private TestConnectorImpl connectorImpl;
+
+ @BeforeEach
+ void setUp() {
+ connectorImpl = new TestConnectorImpl();
+ lenient().when(authorizableLookup.getConnector(CONNECTOR_ID)).thenReturn(connectorAuthorizable);
+
+ final NiFiUser user = new StandardNiFiUser.Builder().identity(USER_IDENTITY).build();
+ final NiFiUserDetails userDetails = new NiFiUserDetails(user);
+ lenient().when(authentication.getPrincipal()).thenReturn(userDetails);
+ lenient().when(securityContext.getAuthentication()).thenReturn(authentication);
+ SecurityContextHolder.setContext(securityContext);
+
+ lenient().when(authorizer.authorize(any(AuthorizationRequest.class))).thenReturn(AuthorizationResult.approved());
+ }
+
+ @AfterEach
+ void tearDown() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ void testReadMethodAuthorizesWithReadAction() {
+ final TestConnector proxy = createProxy();
+
+ final String result = proxy.readData();
+
+ assertEquals("read-result", result);
+ final ArgumentCaptor<RequestAction> actionCaptor = ArgumentCaptor.forClass(RequestAction.class);
+ verify(connectorAuthorizable).authorize(eq(authorizer), actionCaptor.capture(), any(NiFiUser.class));
+ assertEquals(RequestAction.READ, actionCaptor.getValue());
+ }
+
+ @Test
+ void testWriteMethodAuthorizesWithWriteAction() {
+ final TestConnector proxy = createProxy();
+
+ proxy.writeData("test-value");
+
+ assertEquals("test-value", connectorImpl.getLastWrittenValue());
+ final ArgumentCaptor<RequestAction> actionCaptor = ArgumentCaptor.forClass(RequestAction.class);
+ verify(connectorAuthorizable).authorize(eq(authorizer), actionCaptor.capture(), any(NiFiUser.class));
+ assertEquals(RequestAction.WRITE, actionCaptor.getValue());
+ }
+
+ @Test
+ void testMethodWithoutAnnotationThrowsException() {
+ final TestConnector proxy = createProxy();
+
+ final IllegalStateException exception = assertThrows(IllegalStateException.class, proxy::unannotatedMethod);
+ assertEquals(String.format("Method [unannotatedMethod] on connector [%s] is not annotated with "
+ + "@ConnectorWebMethod and cannot be invoked through the Connector Web Context", CONNECTOR_ID), exception.getMessage());
+ }
+
+ @Test
+ void testAuthorizationFailurePropagatesAccessDeniedException() {
+ doThrow(new AccessDeniedException("Access denied for testing"))
+ .when(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+
+ final TestConnector proxy = createProxy();
+
+ assertThrows(AccessDeniedException.class, proxy::readData);
+ }
+
+ @Test
+ void testMethodArgumentsPassedCorrectly() {
+ final TestConnector proxy = createProxy();
+
+ final int result = proxy.processItems(List.of("a", "b", "c"), 10);
+
+ assertEquals(13, result);
+ }
+
+ @Test
+ void testDelegateExceptionUnwrapped() {
+ final TestConnector proxy = createProxy();
+
+ final RuntimeException exception = assertThrows(RuntimeException.class, proxy::throwingMethod);
+ assertEquals("Intentional test exception", exception.getMessage());
+ }
+
+ @Test
+ void testReadMethodWithReturnValue() {
+ final TestConnector proxy = createProxy();
+
+ final List<String> result = proxy.getItems();
+
+ assertEquals(List.of("item1", "item2", "item3"), result);
+ }
+
+ private TestConnector createProxy() {
+ final AuthorizingConnectorInvocationHandler<TestConnector> handler = new AuthorizingConnectorInvocationHandler<>(
+ connectorImpl, CONNECTOR_ID, authorizer, authorizableLookup);
+
+ return (TestConnector) Proxy.newProxyInstance(
+ TestConnector.class.getClassLoader(),
+ new Class<?>[]{TestConnector.class},
+ handler);
+ }
+
+ /**
+ * Test interface representing a Connector with annotated methods.
+ */
+ public interface TestConnector {
+
+ @ConnectorWebMethod(AccessType.READ)
+ String readData();
+
+ @ConnectorWebMethod(AccessType.WRITE)
+ void writeData(String value);
+
+ @ConnectorWebMethod(AccessType.READ)
+ List<String> getItems();
+
+ @ConnectorWebMethod(AccessType.WRITE)
+ int processItems(List<String> items, int multiplier);
+
+ @ConnectorWebMethod(AccessType.READ)
+ void throwingMethod();
+
+ void unannotatedMethod();
+ }
+
+ /**
+ * Test implementation of the TestConnector interface.
+ */
+ public static class TestConnectorImpl implements TestConnector {
+
+ private String lastWrittenValue;
+
+ @Override
+ public String readData() {
+ return "read-result";
+ }
+
+ @Override
+ public void writeData(final String value) {
+ this.lastWrittenValue = value;
+ }
+
+ @Override
+ public List<String> getItems() {
+ return List.of("item1", "item2", "item3");
+ }
+
+ @Override
+ public int processItems(final List<String> items, final int multiplier) {
+ return items.size() + multiplier;
+ }
+
+ @Override
+ public void throwingMethod() {
+ throw new RuntimeException("Intentional test exception");
+ }
+
+ @Override
+ public void unannotatedMethod() {
+ // This method is intentionally not annotated
+ }
+
+ public String getLastWrittenValue() {
+ return lastWrittenValue;
+ }
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingFlowContextTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingFlowContextTest.java
new file mode 100644
index 0000000000..ddc4f277ba
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingFlowContextTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.AuthorizableLookup;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.components.connector.ConnectorConfigurationContext;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.components.connector.components.FlowContextType;
+import org.apache.nifi.components.connector.components.ParameterContextFacade;
+import org.apache.nifi.components.connector.components.ProcessGroupFacade;
+import org.apache.nifi.flow.Bundle;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class AuthorizingFlowContextTest {
+
+ private static final String CONNECTOR_ID = "test-connector-id";
+ private static final String USER_IDENTITY = "test-user";
+
+ @Mock
+ private FlowContext delegate;
+
+ @Mock
+ private Authorizer authorizer;
+
+ @Mock
+ private AuthorizableLookup authorizableLookup;
+
+ @Mock
+ private Authorizable connectorAuthorizable;
+
+ @Mock
+ private ProcessGroupFacade processGroupFacade;
+
+ @Mock
+ private ParameterContextFacade parameterContextFacade;
+
+ @Mock
+ private ConnectorConfigurationContext configurationContext;
+
+ @Mock
+ private SecurityContext securityContext;
+
+ @Mock
+ private Authentication authentication;
+
+ private AuthorizingFlowContext authorizingFlowContext;
+
+ @BeforeEach
+ void setUp() {
+ when(authorizableLookup.getConnector(CONNECTOR_ID)).thenReturn(connectorAuthorizable);
+ lenient().when(authorizer.authorize(any(AuthorizationRequest.class))).thenReturn(AuthorizationResult.approved());
+
+ final NiFiUser user = new StandardNiFiUser.Builder().identity(USER_IDENTITY).build();
+ final NiFiUserDetails userDetails = new NiFiUserDetails(user);
+ when(authentication.getPrincipal()).thenReturn(userDetails);
+ when(securityContext.getAuthentication()).thenReturn(authentication);
+ SecurityContextHolder.setContext(securityContext);
+
+ final ConnectorAuthorizationContext authContext = new ConnectorAuthorizationContext(CONNECTOR_ID, authorizer, authorizableLookup);
+ authorizingFlowContext = new AuthorizingFlowContext(delegate, authContext);
+ }
+
+ @AfterEach
+ void tearDown() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ void testGetRootGroupAuthorizesReadAndReturnsWrappedFacade() {
+ when(delegate.getRootGroup()).thenReturn(processGroupFacade);
+
+ final ProcessGroupFacade result = authorizingFlowContext.getRootGroup();
+
+ assertNotNull(result);
+ assertTrue(result instanceof AuthorizingProcessGroupFacade);
+ verify(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+ }
+
+ @Test
+ void testGetParameterContextAuthorizesReadAndReturnsWrappedFacade() {
+ when(delegate.getParameterContext()).thenReturn(parameterContextFacade);
+
+ final ParameterContextFacade result = authorizingFlowContext.getParameterContext();
+
+ assertNotNull(result);
+ assertTrue(result instanceof AuthorizingParameterContextFacade);
+ verify(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+ }
+
+ @Test
+ void testGetConfigurationContextAuthorizesRead() {
+ when(delegate.getConfigurationContext()).thenReturn(configurationContext);
+
+ final ConnectorConfigurationContext result = authorizingFlowContext.getConfigurationContext();
+
+ assertNotNull(result);
+ verify(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+ }
+
+ @Test
+ void testGetTypeAuthorizesRead() {
+ when(delegate.getType()).thenReturn(FlowContextType.WORKING);
+
+ authorizingFlowContext.getType();
+
+ verify(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+ }
+
+ @Test
+ void testGetBundleAuthorizesRead() {
+ when(delegate.getBundle()).thenReturn(new Bundle("group", "artifact", "version"));
+
+ authorizingFlowContext.getBundle();
+
+ verify(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+ }
+
+ @Test
+ void testAuthorizationFailurePropagates() {
+ doThrow(new AccessDeniedException("Access denied"))
+ .when(connectorAuthorizable).authorize(any(Authorizer.class), any(RequestAction.class), any(NiFiUser.class));
+
+ assertThrows(AccessDeniedException.class, () -> authorizingFlowContext.getRootGroup());
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingParameterContextFacadeTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingParameterContextFacadeTest.java
new file mode 100644
index 0000000000..6fe3cdfeb3
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/authorization/AuthorizingParameterContextFacadeTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.web.connector.authorization;
+
+import org.apache.nifi.asset.Asset;
+import org.apache.nifi.authorization.AuthorizableLookup;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.components.connector.components.ParameterContextFacade;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class AuthorizingParameterContextFacadeTest {
+
+ private static final String CONNECTOR_ID = "test-connector-id";
+ private static final String USER_IDENTITY = "test-user";
+
+ @Mock
+ private ParameterContextFacade delegate;
+
+ @Mock
+ private Authorizer authorizer;
+
+ @Mock
+ private AuthorizableLookup authorizableLookup;
+
+ @Mock
+ private Authorizable connectorAuthorizable;
+
+ @Mock
+ private SecurityContext securityContext;
+
+ @Mock
+ private Authentication authentication;
+
+ @Mock
+ private Asset asset;
+
+ private AuthorizingParameterContextFacade authorizingFacade;
+
+ @BeforeEach
+ void setUp() {
+ when(authorizableLookup.getConnector(CONNECTOR_ID)).thenReturn(connectorAuthorizable);
+ lenient().when(authorizer.authorize(any(AuthorizationRequest.class))).thenReturn(AuthorizationResult.approved());
+
+ final NiFiUser user = new StandardNiFiUser.Builder().identity(USER_IDENTITY).build();
+ final NiFiUserDetails userDetails = new NiFiUserDetails(user);
+ when(authentication.getPrincipal()).thenReturn(userDetails);
+ when(securityContext.getAuthentication()).thenReturn(authentication);
+ SecurityContextHolder.setContext(securityContext);
+
+ final ConnectorAuthorizationContext authContext = new ConnectorAuthorizationContext(CONNECTOR_ID, authorizer, authorizableLookup);
+ authorizingFacade = new AuthorizingParameterContextFacade(delegate, authContext);
+ }
+
+ @AfterEach
+ void tearDown() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ void testGetValueAuthorizesWithReadAction() {
+ when(delegate.getValue("param1")).thenReturn("value1");
+
+ final String result = authorizingFacade.getValue("param1");
+
+ assertEquals("value1", result);
+ final ArgumentCaptor<RequestAction> actionCaptor = ArgumentCaptor.forClass(RequestAction.class);
+ verify(connectorAuthorizable).authorize(eq(authorizer), actionCaptor.capture(), any(NiFiUser.class));
+ assertEquals(RequestAction.READ, actionCaptor.getValue());
+ }
+
+ @Test
+ void testGetDefinedParameterNamesAuthorizesWithReadAction() {
+ when(delegate.getDefinedParameterNames()).thenReturn(Set.of("param1", "param2"));
+
+ final Set<String> result = authorizingFacade.getDefinedParameterNames();
+
+ assertEquals(Set.of("param1", "param2"), result);
+ final ArgumentCaptor<RequestAction> actionCaptor = ArgumentCaptor.forClass(RequestAction.class);
+ verify(connectorAuthorizable).authorize(eq(authorizer), actionCaptor.capture(), any(NiFiUser.class));
+ assertEquals(RequestAction.READ, actionCaptor.getValue());
+ }
+
+ @Test
+ void testIsSensitiveAuthorizesWithReadAction() {
+ when(delegate.isSensitive("param1")).thenReturn(true);
+
+ final boolean result = authorizingFacade.isSensitive("param1");
+
+ assertTrue(result);
+ final ArgumentCaptor<RequestAction> actionCaptor = ArgumentCaptor.forClass(RequestAction.class);
+ verify(connectorAuthorizable).authorize(eq(authorizer), actionCaptor.capture(), any(NiFiUser.class));
+ assertEquals(RequestAction.READ, actionCaptor.getValue());
+ }
+
+ @Test
+ void testUpdateParametersAuthorizesWithWriteAction() {
+ authorizingFacade.updateParameters(Collections.emptyList());
+
+ final ArgumentCaptor<RequestAction> actionCaptor = ArgumentCaptor.forClass(RequestAction.class);
+ verify(connectorAuthorizable).authorize(eq(authorizer), actionCaptor.capture(), any(NiFiUser.class));
+ assertEquals(RequestAction.WRITE, actionCaptor.getValue());
+ verify(delegate).updateParameters(Collections.emptyList());
+ }
+
+ @Test
+ void testCreateAssetAuthorizesWithWriteAction() throws IOException {
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[0]);
+ when(delegate.createAsset(inputStream)).thenReturn(asset);
+
+ final Asset result = authorizingFacade.createAsset(inputStream);
+
+ assertEquals(asset, result);
+ final ArgumentCaptor<RequestAction> actionCaptor = ArgumentCaptor.forClass(RequestAction.class);
+ verify(connectorAuthorizable).authorize(eq(authorizer), actionCaptor.capture(), any(NiFiUser.class));
+ assertEquals(RequestAction.WRITE, actionCaptor.getValue());
+ }
+}
+
(nifi) 14/40: NIFI-15343: Adding an endpoint to return available secrets to the con… (#10645)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 3a33552408ae7d82857e7ea4ce21e40f85b0e197
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Wed Dec 17 14:28:56 2025 -0500
NIFI-15343: Adding an endpoint to return available secrets to the con… (#10645)
* NIFI-15343: Adding an endpoint to return available secrets to the connector configuration wizard.
* NIFI-15343: Adding providerId to SecretDTO.
* NIFI-15343: Skipping ParameterProviders that are invalid or validating when fetching secrets.
* NIFI-15343: Adding fully qualified name to SecretDTO.
- Fixing rebase issue.
This closes #10645
---
.../org/apache/nifi/web/api/dto/SecretDTO.java | 89 ++++++++++++++++++++++
.../apache/nifi/web/api/entity/SecretsEntity.java | 43 +++++++++++
.../secrets/ParameterProviderSecretsManager.java | 6 ++
.../TestParameterProviderSecretsManager.java | 32 ++++++++
.../org/apache/nifi/web/NiFiServiceFacade.java | 13 ++++
.../apache/nifi/web/StandardNiFiServiceFacade.java | 23 ++++++
.../org/apache/nifi/web/api/ConnectorResource.java | 48 ++++++++++++
.../org/apache/nifi/web/api/dto/DtoFactory.java | 23 ++++++
.../org/apache/nifi/web/api/dto/EntityFactory.java | 7 ++
.../nifi/web/controller/ControllerFacade.java | 10 +++
.../nifi/web/StandardNiFiServiceFacadeTest.java | 76 ++++++++++++++++++
.../apache/nifi/web/api/TestConnectorResource.java | 27 +++++++
12 files changed, 397 insertions(+)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/SecretDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/SecretDTO.java
new file mode 100644
index 0000000000..dbbf0f36a0
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/SecretDTO.java
@@ -0,0 +1,89 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+/**
+ * DTO representing a secret's metadata. Note: The actual secret value is never exposed via the REST API.
+ */
+@XmlType(name = "secret")
+public class SecretDTO {
+
+ private String providerId;
+ private String providerName;
+ private String groupName;
+ private String name;
+ private String fullyQualifiedName;
+ private String description;
+
+ @Schema(description = "The identifier of the secret provider that manages this secret.")
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public void setProviderId(final String providerId) {
+ this.providerId = providerId;
+ }
+
+ @Schema(description = "The name of the secret provider that manages this secret.")
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public void setProviderName(final String providerName) {
+ this.providerName = providerName;
+ }
+
+ @Schema(description = "The name of the group this secret belongs to.")
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public void setGroupName(final String groupName) {
+ this.groupName = groupName;
+ }
+
+ @Schema(description = "The name of the secret.")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "The fully qualified name of the secret, including the group name.")
+ public String getFullyQualifiedName() {
+ return fullyQualifiedName;
+ }
+
+ public void setFullyQualifiedName(final String fullyQualifiedName) {
+ this.fullyQualifiedName = fullyQualifiedName;
+ }
+
+ @Schema(description = "A description of the secret.")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/SecretsEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/SecretsEntity.java
new file mode 100644
index 0000000000..0706dde05c
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/SecretsEntity.java
@@ -0,0 +1,43 @@
+/*
+ * 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.web.api.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import org.apache.nifi.web.api.dto.SecretDTO;
+
+import java.util.List;
+
+/**
+ * A serialized representation of this class can be placed in the entity body of a response to the API.
+ * This particular entity holds a list of secrets.
+ */
+@XmlRootElement(name = "secretsEntity")
+public class SecretsEntity extends Entity {
+
+ private List<SecretDTO> secrets;
+
+ @Schema(description = "The list of secrets available from all secret providers.")
+ public List<SecretDTO> getSecrets() {
+ return secrets;
+ }
+
+ public void setSecrets(final List<SecretDTO> secrets) {
+ this.secrets = secrets;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
index d4ba733b08..0fe3715555 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
@@ -22,6 +22,8 @@ import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.controller.ParameterProviderNode;
import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.components.validation.ValidationStatus;
+
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@@ -60,6 +62,10 @@ public class ParameterProviderSecretsManager implements SecretsManager {
public Set<SecretProvider> getSecretProviders() {
final Set<SecretProvider> providers = new HashSet<>();
for (final ParameterProviderNode parameterProviderNode : flowManager.getAllParameterProviders()) {
+ if (parameterProviderNode.getValidationStatus() != ValidationStatus.VALID) {
+ continue;
+ }
+
providers.add(new ParameterProviderSecretProvider(parameterProviderNode));
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java
index b193239ed3..2ccb772f09 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java
@@ -18,6 +18,7 @@ package org.apache.nifi.components.connector.secrets;
import org.apache.nifi.components.connector.Secret;
import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.controller.ParameterProviderNode;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.parameter.Parameter;
@@ -85,9 +86,15 @@ public class TestParameterProviderSecretsManager {
}
private ParameterProviderNode createMockedParameterProviderNode(final String id, final String name, final String groupName, final Parameter... parameters) {
+ return createMockedParameterProviderNode(id, name, groupName, ValidationStatus.VALID, parameters);
+ }
+
+ private ParameterProviderNode createMockedParameterProviderNode(final String id, final String name, final String groupName,
+ final ValidationStatus validationStatus, final Parameter... parameters) {
final ParameterProviderNode node = mock(ParameterProviderNode.class);
when(node.getIdentifier()).thenReturn(id);
when(node.getName()).thenReturn(name);
+ when(node.getValidationStatus()).thenReturn(validationStatus);
final List<Parameter> parameterList = List.of(parameters);
final ParameterGroup group = new ParameterGroup(groupName, parameterList);
@@ -319,5 +326,30 @@ public class TestParameterProviderSecretsManager {
assertTrue(result.isPresent());
assertEquals(PROVIDER_1_NAME, result.get().getProviderName());
}
+
+ @Test
+ public void testGetSecretProvidersFiltersOutInvalidProviders() {
+ final FlowManager flowManager = mock(FlowManager.class);
+ final ParameterProviderNode validProvider = createMockedParameterProviderNode("valid-id", "Valid Provider", "Group",
+ ValidationStatus.VALID, createParameter("secret", "description", "value"));
+ final ParameterProviderNode invalidProvider = createMockedParameterProviderNode("invalid-id", "Invalid Provider", "Group",
+ ValidationStatus.INVALID, createParameter("secret2", "description2", "value2"));
+ final ParameterProviderNode validatingProvider = createMockedParameterProviderNode("validating-id", "Validating Provider", "Group",
+ ValidationStatus.VALIDATING, createParameter("secret3", "description3", "value3"));
+
+ final Set<ParameterProviderNode> allProviders = new HashSet<>();
+ allProviders.add(validProvider);
+ allProviders.add(invalidProvider);
+ allProviders.add(validatingProvider);
+ when(flowManager.getAllParameterProviders()).thenReturn(allProviders);
+
+ final ParameterProviderSecretsManager manager = new ParameterProviderSecretsManager();
+ manager.initialize(new StandardSecretsManagerInitializationContext(flowManager));
+
+ final Set<SecretProvider> secretProviders = manager.getSecretProviders();
+
+ assertEquals(1, secretProviders.size());
+ assertEquals("valid-id", secretProviders.iterator().next().getProviderId());
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 7fcef99ee2..7e8a53c8a1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -161,6 +161,7 @@ import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
import org.apache.nifi.web.api.entity.UserEntity;
import org.apache.nifi.web.api.entity.UserGroupEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.VersionControlComponentMappingEntity;
import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
import org.apache.nifi.web.api.entity.VersionedFlowEntity;
@@ -3197,4 +3198,16 @@ public interface NiFiServiceFacade {
* @return the list of listen Ports accessible to the current user
*/
Set<ListenPortDTO> getListenPorts(NiFiUser user);
+
+ // ----------------------------------------
+ // Secrets methods
+ // ----------------------------------------
+
+ /**
+ * Gets all secrets available from all secret providers. Note: The actual secret values are not included
+ * in the response for security reasons; only metadata is returned.
+ *
+ * @return the secrets entity containing metadata for all available secrets
+ */
+ SecretsEntity getSecrets();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 6852c9ee7b..6fb12571f2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -80,6 +80,8 @@ import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorUpdateContext;
+import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.components.connector.secrets.AuthorizableSecret;
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.components.validation.ValidationState;
@@ -295,6 +297,7 @@ import org.apache.nifi.web.api.dto.ReportingTaskDTO;
import org.apache.nifi.web.api.dto.RequiredPermissionDTO;
import org.apache.nifi.web.api.dto.ResourceDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.dto.SecretDTO;
import org.apache.nifi.web.api.dto.SnippetDTO;
import org.apache.nifi.web.api.dto.SystemDiagnosticsDTO;
import org.apache.nifi.web.api.dto.TenantDTO;
@@ -392,6 +395,7 @@ import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.SnippetEntity;
import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
@@ -7773,4 +7777,23 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
return getComponents.apply(processGroup);
}
+
+ @Override
+ public SecretsEntity getSecrets() {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final List<Secret> secrets = controllerFacade.getAllSecrets();
+ final List<SecretDTO> secretDtos = secrets.stream()
+ .filter(secret -> isSecretAuthorized(secret, user))
+ .map(dtoFactory::createSecretDto)
+ .toList();
+ return entityFactory.createSecretsEntity(secretDtos);
+ }
+
+ private boolean isSecretAuthorized(final Secret secret, final NiFiUser user) {
+ if (secret instanceof AuthorizableSecret authorizableSecret) {
+ final AuthorizationResult result = authorizableSecret.checkAuthorization(authorizer, RequestAction.READ, user);
+ return Result.Approved.equals(result.getResult());
+ }
+ return true;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
index ffc3fc264d..e5ad85ea1a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
@@ -65,6 +65,7 @@ import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.SearchResultsEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.concurrent.AsyncRequestManager;
import org.apache.nifi.web.api.concurrent.AsynchronousWebRequest;
import org.apache.nifi.web.api.concurrent.RequestManager;
@@ -313,6 +314,53 @@ public class ConnectorResource extends ApplicationResource {
return generateOkResponse(entity).build();
}
+ /**
+ * Gets all available secrets from the SecretsManager for configuring a specific connector.
+ *
+ * @param id The id of the connector being configured
+ * @return A secretsEntity containing metadata for all available secrets.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/secrets")
+ @Operation(
+ summary = "Gets all secrets available for configuring a connector",
+ description = "Returns metadata for all secrets available from all secret providers. "
+ + "This endpoint is used when configuring a connector to discover available secrets. "
+ + "Note: Actual secret values are not included in the response for security reasons.",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = SecretsEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid}")
+ }
+ )
+ public Response getSecrets(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id) {
+
+ // NOTE: fetching secrets is handled by the node that receives the request and does not need to be replicated
+ // Secrets are sourced from ParameterProviders which should have consistent configuration across the cluster
+
+ // authorize access - require write permission on the specific connector since this is used for configuration
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+ });
+
+ final SecretsEntity entity = serviceFacade.getSecrets();
+ return generateOkResponse(entity).build();
+ }
+
/**
* Updates the specified connector.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 4d26d4d7ab..e48a59325b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -80,6 +80,7 @@ import org.apache.nifi.components.connector.ConnectorPropertyGroup;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FrameworkFlowContext;
import org.apache.nifi.components.connector.NamedStepConfiguration;
+import org.apache.nifi.components.connector.Secret;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
@@ -5436,4 +5437,26 @@ public final class DtoFactory {
return dto;
}
+ /**
+ * Creates a SecretDTO from the specified Secret. Note: The secret value is intentionally not included
+ * in the DTO for security reasons.
+ *
+ * @param secret the secret
+ * @return the DTO containing only the secret's metadata
+ */
+ public SecretDTO createSecretDto(final Secret secret) {
+ if (secret == null) {
+ return null;
+ }
+
+ final SecretDTO dto = new SecretDTO();
+ dto.setProviderId(secret.getProviderId());
+ dto.setProviderName(secret.getProviderName());
+ dto.setGroupName(secret.getGroupName());
+ dto.setName(secret.getName());
+ dto.setFullyQualifiedName(secret.getFullyQualifiedName());
+ dto.setDescription(secret.getDescription());
+ return dto;
+ }
+
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
index 144f7805fa..9d51b58e65 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
@@ -83,6 +83,7 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusSnapshotEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
import org.apache.nifi.web.api.entity.SnippetEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
@@ -921,4 +922,10 @@ public final class EntityFactory {
entity.setAllowableValues(allowableValues);
return entity;
}
+
+ public SecretsEntity createSecretsEntity(final List<SecretDTO> secrets) {
+ final SecretsEntity entity = new SecretsEntity();
+ entity.setSecrets(secrets);
+ return entity;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index c75e827375..63611f6a1f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -46,6 +46,7 @@ import org.apache.nifi.components.RequiredPermission;
import org.apache.nifi.components.listen.ListenComponent;
import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.Secret;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.connectable.Port;
@@ -2004,6 +2005,15 @@ public class ControllerFacade implements Authorizable {
return new StandardVersionedReportingTaskImporter(flowController);
}
+ /**
+ * Gets all secrets from the SecretsManager.
+ *
+ * @return list of all secrets available from all secret providers
+ */
+ public List<Secret> getAllSecrets() {
+ return flowController.getConnectorRepository().getSecretsManager().getAllSecrets();
+ }
+
/*
* setters
*/
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
index e5270f871b..016e5220ad 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
@@ -38,6 +38,8 @@ import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.components.connector.secrets.AuthorizableSecret;
import org.apache.nifi.controller.Counter;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.ProcessorNode;
@@ -104,6 +106,7 @@ import org.apache.nifi.web.api.entity.ClearBulletinsForGroupResultsEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsResultEntity;
import org.apache.nifi.web.api.entity.CopyRequestEntity;
import org.apache.nifi.web.api.entity.CopyResponseEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
@@ -1703,4 +1706,77 @@ public class StandardNiFiServiceFacadeTest {
assertNotNull(result);
assertEquals(0, result.getBulletinsCleared());
}
+
+ @Test
+ public void testGetSecretsFiltersUnauthorizedSecrets() {
+ final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build()));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ final ControllerFacade controllerFacade = mock(ControllerFacade.class);
+ serviceFacade.setControllerFacade(controllerFacade);
+
+ final AuthorizableSecret authorizedSecret = mock(AuthorizableSecret.class);
+ when(authorizedSecret.getProviderName()).thenReturn("provider1");
+ when(authorizedSecret.getGroupName()).thenReturn("group1");
+ when(authorizedSecret.getName()).thenReturn("authorized-secret");
+ when(authorizedSecret.getDescription()).thenReturn("An authorized secret");
+ when(authorizedSecret.checkAuthorization(any(Authorizer.class), any(), any())).thenReturn(AuthorizationResult.approved());
+
+ final AuthorizableSecret unauthorizedSecret = mock(AuthorizableSecret.class);
+ when(unauthorizedSecret.getProviderName()).thenReturn("provider2");
+ when(unauthorizedSecret.getGroupName()).thenReturn("group2");
+ when(unauthorizedSecret.getName()).thenReturn("unauthorized-secret");
+ when(unauthorizedSecret.getDescription()).thenReturn("An unauthorized secret");
+ when(unauthorizedSecret.checkAuthorization(any(Authorizer.class), any(), any())).thenReturn(AuthorizationResult.denied());
+
+ when(controllerFacade.getAllSecrets()).thenReturn(List.of(authorizedSecret, unauthorizedSecret));
+
+ final SecretsEntity result = serviceFacade.getSecrets();
+
+ assertNotNull(result);
+ assertNotNull(result.getSecrets());
+ assertEquals(1, result.getSecrets().size());
+ assertEquals("authorized-secret", result.getSecrets().get(0).getName());
+ }
+
+ @Test
+ public void testGetSecretsWithNonAuthorizableSecrets() {
+ final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build()));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ final ControllerFacade controllerFacade = mock(ControllerFacade.class);
+ serviceFacade.setControllerFacade(controllerFacade);
+
+ final Secret nonAuthorizableSecret = mock(Secret.class);
+ when(nonAuthorizableSecret.getProviderName()).thenReturn("provider1");
+ when(nonAuthorizableSecret.getGroupName()).thenReturn("group1");
+ when(nonAuthorizableSecret.getName()).thenReturn("non-authorizable-secret");
+ when(nonAuthorizableSecret.getDescription()).thenReturn("A non-authorizable secret");
+
+ when(controllerFacade.getAllSecrets()).thenReturn(List.of(nonAuthorizableSecret));
+
+ final SecretsEntity result = serviceFacade.getSecrets();
+
+ assertNotNull(result);
+ assertNotNull(result.getSecrets());
+ assertEquals(1, result.getSecrets().size());
+ assertEquals("non-authorizable-secret", result.getSecrets().get(0).getName());
+ }
+
+ @Test
+ public void testGetSecretsWithEmptyList() {
+ final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build()));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ final ControllerFacade controllerFacade = mock(ControllerFacade.class);
+ serviceFacade.setControllerFacade(controllerFacade);
+
+ when(controllerFacade.getAllSecrets()).thenReturn(List.of());
+
+ final SecretsEntity result = serviceFacade.getSecrets();
+
+ assertNotNull(result);
+ assertNotNull(result.getSecrets());
+ assertTrue(result.getSecrets().isEmpty());
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
index e1093d266d..df21a6935e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
@@ -35,6 +35,7 @@ import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.nifi.web.api.request.LongParameter;
import org.junit.jupiter.api.BeforeEach;
@@ -326,6 +327,32 @@ public class TestConnectorResource {
verify(serviceFacade, never()).getConnectorPropertyAllowableValues(anyString(), anyString(), anyString(), anyString(), any());
}
+ @Test
+ public void testGetSecrets() {
+ final SecretsEntity responseEntity = new SecretsEntity();
+ responseEntity.setSecrets(List.of());
+
+ when(serviceFacade.getSecrets()).thenReturn(responseEntity);
+
+ try (Response response = connectorResource.getSecrets(CONNECTOR_ID)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(responseEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade).getSecrets();
+ }
+
+ @Test
+ public void testGetSecretsNotAuthorized() {
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () -> connectorResource.getSecrets(CONNECTOR_ID));
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade, never()).getSecrets();
+ }
+
private ConnectorEntity createConnectorEntity() {
final ConnectorEntity entity = new ConnectorEntity();
(nifi) 29/40: NIFI-15439: Ensure that Process Groups are accessible from the approp… (#10742)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 4c98a8842f50d118af1aa36318af406164a2c56f
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Mon Jan 12 14:30:06 2026 -0500
NIFI-15439: Ensure that Process Groups are accessible from the approp… (#10742)
* NIFI-15439: Ensure that Process Groups are accessible from the appropriate endpoint but not accessible elsewhere
* NIFI-15439: FlowAnalyzingRegistryClient should use an explicit Connector ID of null when retrieving Process Group from FlowManager
---
.../nifi/controller/flow/AbstractFlowManager.java | 16 +++++
.../apache/nifi/groups/StandardProcessGroup.java | 2 +-
.../flow/FlowAnalyzingRegistryClientNode.java | 3 +-
.../flow/FlowAnalyzingRegistryClientNodeTest.java | 2 +-
.../apache/nifi/controller/flow/FlowManager.java | 9 +++
.../org/apache/nifi/controller/FlowController.java | 10 +--
.../nifi/controller/flow/StandardFlowManager.java | 7 +-
.../org/apache/nifi/web/dao/impl/ComponentDAO.java | 4 +-
.../nifi/web/dao/impl/StandardProcessGroupDAO.java | 4 +-
.../org/apache/nifi/audit/TestLabelAuditor.java | 2 +-
.../apache/nifi/audit/TestProcessGroupAuditor.java | 6 +-
.../apache/nifi/audit/TestProcessorAuditor.java | 2 +-
.../web/dao/impl/StandardProcessorDAOTest.java | 2 +-
.../web/dao/impl/TestStandardProcessGroupDAO.java | 15 +----
.../tests/system/NestedProcessGroupConnector.java | 75 ++++++++++++++++++++++
.../org.apache.nifi.components.connector.Connector | 1 +
.../tests/system/connectors/ConnectorCrudIT.java | 49 ++++++++++++++
.../nifi/toolkit/client/ConnectorClient.java | 6 +-
.../toolkit/client/impl/JerseyConnectorClient.java | 18 +++---
19 files changed, 185 insertions(+), 48 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
index a694c5be35..076f2efa99 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
@@ -63,6 +63,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@@ -122,6 +123,21 @@ public abstract class AbstractFlowManager implements FlowManager {
return allProcessGroups.get(requireNonNull(id));
}
+ @Override
+ public ProcessGroup getGroup(final String groupId, final String connectorId) {
+ final ProcessGroup group = allProcessGroups.get(requireNonNull(groupId));
+ if (group == null) {
+ return null;
+ }
+
+ // If we found the group, return it only if it has the correct connector ID
+ if (Objects.equals(group.getConnectorIdentifier().orElse(null), connectorId)) {
+ return group;
+ }
+
+ return null;
+ }
+
@Override
public void onProcessGroupAdded(final ProcessGroup group) {
allProcessGroups.put(group.getIdentifier(), group);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
index 9918eda3a1..3987ea19f9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
@@ -2112,7 +2112,7 @@ public final class StandardProcessGroup implements ProcessGroup {
return this;
}
- final ProcessGroup group = flowManager.getGroup(id);
+ final ProcessGroup group = flowManager.getGroup(id, getConnectorIdentifier().orElse(null));
if (group == null) {
return null;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNode.java
index 14d555d013..faeb336efe 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNode.java
@@ -111,7 +111,8 @@ public final class FlowAnalyzingRegistryClientNode implements FlowRegistryClient
}
private boolean analyzeProcessGroupToRegister(final VersionedProcessGroup snapshot) {
- final InstantiatedVersionedProcessGroup nonVersionedProcessGroup = flowMapper.mapNonVersionedProcessGroup(flowManager.getGroup(snapshot.getInstanceIdentifier()), serviceProvider);
+ final ProcessGroup group = flowManager.getGroup(snapshot.getInstanceIdentifier(), null);
+ final InstantiatedVersionedProcessGroup nonVersionedProcessGroup = flowMapper.mapNonVersionedProcessGroup(group, serviceProvider);
flowAnalyzer.analyzeProcessGroup(nonVersionedProcessGroup);
final List<RuleViolation> ruleViolations = ruleViolationsManager.getRuleViolationsForGroup(snapshot.getInstanceIdentifier()).stream()
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNodeTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNodeTest.java
index 5e3f2ce46a..e69a27a05f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNodeTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNodeTest.java
@@ -90,7 +90,7 @@ class FlowAnalyzingRegistryClientNodeTest {
@BeforeEach
public void setUp() {
Mockito.when(versionedProcessGroup.getInstanceIdentifier()).thenReturn(INSTANCE_IDENTIFIER);
- Mockito.when(flowManager.getGroup(Mockito.anyString())).thenReturn(processGroup);
+ Mockito.when(flowManager.getGroup(Mockito.anyString(), Mockito.eq(null))).thenReturn(processGroup);
Mockito.when(flowMapper.mapNonVersionedProcessGroup(Mockito.same(processGroup), Mockito.same(serviceProvider))).thenReturn(nonVersionedProcessGroup);
Mockito.when(ruleViolation1.getEnforcementPolicy()).thenReturn(EnforcementPolicy.ENFORCE);
Mockito.when(ruleViolation2.getEnforcementPolicy()).thenReturn(EnforcementPolicy.ENFORCE);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
index d756e8531e..c1a363a5ab 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
@@ -165,6 +165,15 @@ public interface FlowManager extends ParameterProviderLookup {
*/
ProcessGroup getGroup(String id);
+ /**
+ * Returns the ProcessGroup with the given ID that is managed by the Connector with the given ID,
+ * or null if no such ProcessGroup exists.
+ * @param groupId id of the group
+ * @param connectorId id of the connector
+ * @return the ProcessGroup with the given ID or null if none can be found
+ */
+ ProcessGroup getGroup(String groupId, String connectorId);
+
void onProcessGroupAdded(ProcessGroup group);
void onProcessGroupRemoved(ProcessGroup group);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index 81a7115ada..1bea151791 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -52,14 +52,14 @@ import org.apache.nifi.components.ClassLoaderAwarePythonBridge;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorRepositoryInitializationContext;
import org.apache.nifi.components.connector.ConnectorRequestReplicator;
+import org.apache.nifi.components.connector.ConnectorValidationTrigger;
import org.apache.nifi.components.connector.StandardConnectorRepoInitializationContext;
import org.apache.nifi.components.connector.StandardConnectorRepository;
+import org.apache.nifi.components.connector.StandardConnectorValidationTrigger;
import org.apache.nifi.components.connector.secrets.ParameterProviderSecretsManager;
import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.components.connector.secrets.SecretsManagerInitializationContext;
import org.apache.nifi.components.connector.secrets.StandardSecretsManagerInitializationContext;
-import org.apache.nifi.components.connector.ConnectorValidationTrigger;
-import org.apache.nifi.components.connector.StandardConnectorValidationTrigger;
import org.apache.nifi.components.monitor.LongRunningTaskMonitor;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.components.state.StateProvider;
@@ -229,8 +229,6 @@ import org.apache.nifi.web.revision.RevisionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.management.NotificationEmitter;
-import javax.net.ssl.SSLContext;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
@@ -263,6 +261,8 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import java.util.stream.Collectors;
+import javax.management.NotificationEmitter;
+import javax.net.ssl.SSLContext;
import static java.util.Objects.requireNonNull;
@@ -2180,7 +2180,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
* @return the process group or null if not group is found
*/
private ProcessGroup lookupGroup(final String id) {
- final ProcessGroup group = flowManager.getGroup(id);
+ final ProcessGroup group = flowManager.getGroup(id, null);
if (group == null) {
throw new IllegalStateException("No Group with ID " + id + " exists");
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 96bbd8f4f7..d3874eb5cb 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -306,10 +306,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
flowController.getReloadComponent(), flowController, nifiProperties, statelessGroupNodeFactory,
flowController.getAssetManager(), connectorId);
- // We don't want to register the group if it's being created as part of a Connector
- if (connectorId == null) {
- onProcessGroupAdded(group);
- }
+ onProcessGroupAdded(group);
return group;
}
@@ -750,7 +747,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
final ExtensionManager extensionManager = flowController.getExtensionManager();
final String managedGroupId = UUID.nameUUIDFromBytes((id + "-root").getBytes(StandardCharsets.UTF_8)).toString();
- final ProcessGroup managedRootGroup = createProcessGroup(managedGroupId);
+ final ProcessGroup managedRootGroup = createProcessGroup(managedGroupId, id);
final String paramContextId = UUID.nameUUIDFromBytes((id + "-parameter-context").getBytes(StandardCharsets.UTF_8)).toString();
final String paramContextName = "Connector " + id + " Parameter Context";
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/ComponentDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/ComponentDAO.java
index 078644a24a..90760cbcd3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/ComponentDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/ComponentDAO.java
@@ -63,8 +63,8 @@ public abstract class ComponentDAO {
* @param groupId id
* @return group
*/
- protected ProcessGroup locateProcessGroup(FlowController flowController, String groupId) {
- ProcessGroup group = flowController.getFlowManager().getGroup(groupId);
+ protected ProcessGroup locateProcessGroup(final FlowController flowController, final String groupId) {
+ ProcessGroup group = flowController.getFlowManager().getGroup(groupId, null);
if (group == null) {
throw new ResourceNotFoundException(String.format("Unable to locate group with id '%s'.", groupId));
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java
index f464a7375e..aa7c5bf736 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java
@@ -120,7 +120,7 @@ public class StandardProcessGroupDAO extends ComponentDAO implements ProcessGrou
@Override
public boolean hasProcessGroup(String groupId) {
- return flowController.getFlowManager().getGroup(groupId) != null;
+ return flowController.getFlowManager().getGroup(groupId, null) != null;
}
@Override
@@ -411,7 +411,7 @@ public class StandardProcessGroupDAO extends ComponentDAO implements ProcessGrou
.map(flowManager::getControllerServiceNode)
.collect(Collectors.toList());
- final ProcessGroup group = flowManager.getGroup(groupId);
+ final ProcessGroup group = flowManager.getGroup(groupId, null);
if (group == null) {
throw new IllegalArgumentException("Cannot activate Controller Services with IDs " + serviceIds + " because the associated Process Group (id=" + groupId + ") could not be found");
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestLabelAuditor.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestLabelAuditor.java
index af03389ec3..48605cd64f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestLabelAuditor.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestLabelAuditor.java
@@ -119,7 +119,7 @@ public class TestLabelAuditor {
@Test
void testCreateLabelAdvice() {
final LabelDTO labelDto = getLabelDto();
- when(flowManager.getGroup(eq(GROUP_ID))).thenReturn(processGroup);
+ when(flowManager.getGroup(eq(GROUP_ID), eq(null))).thenReturn(processGroup);
when(flowManager.createLabel(eq(LABEL_ID), eq(LABEL))).thenReturn(new StandardLabel(LABEL_ID, LABEL));
final Label label = labelDao.createLabel(GROUP_ID, labelDto);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestProcessGroupAuditor.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestProcessGroupAuditor.java
index 3d5f5bd1ac..b79ef9e344 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestProcessGroupAuditor.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestProcessGroupAuditor.java
@@ -176,7 +176,7 @@ public class TestProcessGroupAuditor {
when(processGroup.findRemoteGroupPort(REMOTE_GROUP_INPUT_PORT)).thenReturn(remoteGroupInputPort);
when(remoteGroupInputPort.getRemoteProcessGroup()).thenReturn(remoteProcessGroup);
- when(flowManager.getGroup(eq(PG_1))).thenReturn(processGroup);
+ when(flowManager.getGroup(eq(PG_1), eq(null))).thenReturn(processGroup);
when(flowManager.findConnectable(eq(PROC_1))).thenReturn(processor1);
when(flowManager.findConnectable(eq(PROC_2))).thenReturn(processor2);
when(flowManager.findConnectable(eq(INPUT_PORT))).thenReturn(inputPort);
@@ -286,7 +286,7 @@ public class TestProcessGroupAuditor {
when(processGroup.findInputPort(OUTPUT_PORT)).thenReturn(null);
when(processGroup.findOutputPort(OUTPUT_PORT)).thenReturn(outputPort);
- when(flowManager.getGroup(eq(PG_1))).thenReturn(processGroup);
+ when(flowManager.getGroup(eq(PG_1), eq(null))).thenReturn(processGroup);
when(flowManager.findConnectable(eq(PROC_1))).thenReturn(processor1);
when(flowManager.findConnectable(eq(PROC_2))).thenReturn(processor2);
when(flowManager.findConnectable(eq(INPUT_PORT))).thenReturn(inputPort);
@@ -354,7 +354,7 @@ public class TestProcessGroupAuditor {
when(cs.getName()).thenReturn(CS_1);
when(processGroup.findControllerService(eq(CS_1), eq(true), eq(true))).thenReturn(cs);
- when(flowManager.getGroup(eq(PG_1))).thenReturn(processGroup);
+ when(flowManager.getGroup(eq(PG_1), eq(null))).thenReturn(processGroup);
when(flowManager.getControllerServiceNode(eq(CS_1))).thenReturn(cs);
when(flowController.getFlowManager()).thenReturn(flowManager);
when(flowController.getControllerServiceProvider()).thenReturn(csProvider);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestProcessorAuditor.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestProcessorAuditor.java
index 2b249b362b..99ebc25eae 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestProcessorAuditor.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestProcessorAuditor.java
@@ -147,7 +147,7 @@ class TestProcessorAuditor {
when(flowController.getControllerServiceProvider()).thenReturn(mock(ControllerServiceProvider.class));
when(flowController.getStateManagerProvider()).thenReturn(mockStateManagerProvider);
- when(flowManager.getGroup(GROUP_ID)).thenReturn(processGroup);
+ when(flowManager.getGroup(GROUP_ID, null)).thenReturn(processGroup);
when(flowManager.createProcessor(anyString(), anyString(), any())).thenReturn(mockProcessorNode);
final Bundle bundle = getBundle();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardProcessorDAOTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardProcessorDAOTest.java
index 30c9fdfdc9..1ae61d3dce 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardProcessorDAOTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardProcessorDAOTest.java
@@ -210,7 +210,7 @@ class StandardProcessorDAOTest {
final Bundle bundle = new Bundle(bundleDetails, getClass().getClassLoader());
final List<Bundle> bundles = List.of(bundle);
- when(flowManager.getGroup(eq(groupId))).thenReturn(processGroup);
+ when(flowManager.getGroup(eq(groupId), eq(null))).thenReturn(processGroup);
when(flowController.getExtensionManager()).thenReturn(extensionManager);
when(flowManager.createProcessor(eq(processorType), eq(id), eq(bundleCoordinate))).thenReturn(processorNode);
when(extensionManager.getBundles(eq(processorType))).thenReturn(bundles);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardProcessGroupDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardProcessGroupDAO.java
index 9ca3f0a837..85c55f88f9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardProcessGroupDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/TestStandardProcessGroupDAO.java
@@ -59,21 +59,12 @@ public class TestStandardProcessGroupDAO {
testSubject = new StandardProcessGroupDAO();
testSubject.setFlowController(flowController);
- when(flowController
- .getFlowManager()
- .getGroup(PARENT_PROCESS_GROUP_ID)
- ).thenReturn(parentProcessGroup);
-
- when(flowController
- .getFlowManager()
- .getParameterContextManager()
- .getParameterContext(PARAMETER_CONTEXT_ID)
- ).thenReturn(parameterContext);
+ when(flowController.getFlowManager().getGroup(PARENT_PROCESS_GROUP_ID, null)).thenReturn(parentProcessGroup);
+ when(flowController.getFlowManager().getParameterContextManager().getParameterContext(PARAMETER_CONTEXT_ID)).thenReturn(parameterContext);
}
@Test
public void testCreateProcessGroup() {
- //GIVEN
ParameterContextReferenceEntity parameterContextReferenceEntity = new ParameterContextReferenceEntity();
parameterContextReferenceEntity.setId(PARAMETER_CONTEXT_ID);
@@ -84,10 +75,8 @@ public class TestStandardProcessGroupDAO {
processGroupDTO.setPosition(new PositionDTO(10.0, 20.0));
processGroupDTO.setParameterContext(parameterContextReferenceEntity);
- //WHEN
ProcessGroup createdProcessGroup = testSubject.createProcessGroup(PARENT_PROCESS_GROUP_ID, processGroupDTO);
- //THEN
verify(createdProcessGroup).setParent(parentProcessGroup);
verify(createdProcessGroup).setParameterContext(parameterContext);
verify(createdProcessGroup).setName(PROCESS_GROUP_NAME);
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NestedProcessGroupConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NestedProcessGroupConnector.java
new file mode 100644
index 0000000000..e3903a38f1
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NestedProcessGroupConnector.java
@@ -0,0 +1,75 @@
+/*
+ * 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.connectors.tests.system;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.connector.AbstractConnector;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedProcessGroup;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test Connector that creates a flow with a nested (child) process group.
+ * Used to verify that child process groups within a connector's managed flow
+ * can be properly accessed via the connector API.
+ */
+public class NestedProcessGroupConnector extends AbstractConnector {
+
+ public static final String CHILD_PROCESS_GROUP_ID = "child-process-group-id";
+
+ private final List<ConfigurationStep> configurationSteps = List.of();
+
+ @Override
+ protected void onStepConfigured(final String stepName, final FlowContext workingContext) {
+ }
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ final VersionedProcessGroup childGroup = new VersionedProcessGroup();
+ childGroup.setIdentifier(CHILD_PROCESS_GROUP_ID);
+ childGroup.setName("Child Process Group");
+
+ final VersionedProcessGroup rootGroup = new VersionedProcessGroup();
+ rootGroup.setName("Nested Flow");
+ rootGroup.setProcessGroups(Set.of(childGroup));
+
+ final VersionedExternalFlow flow = new VersionedExternalFlow();
+ flow.setFlowContents(rootGroup);
+ return flow;
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> propertyValueOverrides, final FlowContext flowContext) {
+ return List.of();
+ }
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return configurationSteps;
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingFlowContext, final FlowContext activeFlowContext) {
+ }
+}
+
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
index c8e0d0ed64..a54f75d132 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -16,3 +16,4 @@
org.apache.nifi.connectors.tests.system.NopConnector
org.apache.nifi.connectors.tests.system.AssetConnector
org.apache.nifi.connectors.tests.system.DataQueuingConnector
+org.apache.nifi.connectors.tests.system.NestedProcessGroupConnector
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
index fb616c6d4b..c1098c4d19 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
@@ -17,6 +17,7 @@
package org.apache.nifi.tests.system.connectors;
+import jakarta.ws.rs.NotFoundException;
import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.tests.system.NiFiSystemIT;
@@ -24,18 +25,24 @@ import org.apache.nifi.toolkit.client.NiFiClientException;
import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
+import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.ParameterContextsEntity;
import org.apache.nifi.web.api.entity.ParameterProviderEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
public class ConnectorCrudIT extends NiFiSystemIT {
@@ -162,4 +169,46 @@ public class ConnectorCrudIT extends NiFiSystemIT {
// Delete
getNifiClient().getConnectorClient().deleteConnector(connector);
}
+
+ @Test
+ public void testProcessGroupAccessibility() throws NiFiClientException, IOException {
+ final ConnectorEntity connector = getClientUtil().createConnector("NestedProcessGroupConnector");
+ assertNotNull(connector);
+
+ final ConnectorEntity updatedConnector = getNifiClient().getConnectorClient().getConnector(connector.getId());
+ final String managedProcessGroupId = updatedConnector.getComponent().getManagedProcessGroupId();
+ assertNotNull(managedProcessGroupId);
+
+ final ProcessGroupFlowEntity connectorFlowEntity = getNifiClient().getConnectorClient().getFlow(connector.getId(), managedProcessGroupId);
+ assertNotNull(connectorFlowEntity);
+
+ final ProcessGroupFlowDTO connectorFlow = connectorFlowEntity.getProcessGroupFlow();
+ assertNotNull(connectorFlow);
+ assertEquals(managedProcessGroupId, connectorFlow.getId());
+
+ try {
+ getNifiClient().getFlowClient().getProcessGroup(managedProcessGroupId);
+ fail("Was able to retrieve connector-managed process group via FlowClient");
+ } catch (final NiFiClientException e) {
+ assertInstanceOf(NotFoundException.class, e.getCause());
+ }
+
+ final Set<ProcessGroupEntity> childGroups = connectorFlow.getFlow().getProcessGroups();
+ assertEquals(1, childGroups.size(), "Expected exactly one child process group");
+
+ final ProcessGroupEntity childGroup = childGroups.iterator().next();
+ final String childGroupId = childGroup.getId();
+ assertNotNull(childGroupId);
+
+ final ProcessGroupFlowEntity childFlowEntity = getNifiClient().getConnectorClient().getFlow(connector.getId(), childGroupId);
+ assertNotNull(childFlowEntity);
+ assertEquals(childGroupId, childFlowEntity.getProcessGroupFlow().getId());
+
+ try {
+ getNifiClient().getFlowClient().getProcessGroup(childGroupId);
+ fail("Was able to retrieve child process group of connector-managed flow via FlowClient");
+ } catch (final NiFiClientException e) {
+ assertInstanceOf(NotFoundException.class, e.getCause());
+ }
+ }
}
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
index 923604ba5f..1e95222225 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
@@ -234,15 +234,15 @@ public interface ConnectorClient {
ProcessGroupFlowEntity getFlow(String connectorId) throws NiFiClientException, IOException;
/**
- * Gets the flow for the process group managed by a connector.
+ * Gets the flow for a specific process group within a connector's managed flow.
*
* @param connectorId the connector ID
- * @param uiOnly whether to return only UI-specific fields
+ * @param processGroupId the process group ID within the connector's managed flow
* @return the process group flow entity
* @throws NiFiClientException if an error occurs during the request
* @throws IOException if an I/O error occurs
*/
- ProcessGroupFlowEntity getFlow(String connectorId, boolean uiOnly) throws NiFiClientException, IOException;
+ ProcessGroupFlowEntity getFlow(String connectorId, String processGroupId) throws NiFiClientException, IOException;
/**
* Gets the status for the process group managed by a connector.
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
index 8df19b09d5..b5ad5878d5 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
@@ -40,11 +40,11 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.util.Objects;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.util.Objects;
/**
* Jersey implementation of ConnectorClient.
@@ -361,21 +361,21 @@ public class JerseyConnectorClient extends AbstractJerseyClient implements Conne
@Override
public ProcessGroupFlowEntity getFlow(final String connectorId) throws NiFiClientException, IOException {
- return getFlow(connectorId, false);
+ final ConnectorEntity connector = getConnector(connectorId);
+ final String managedProcessGroupId = connector.getComponent().getManagedProcessGroupId();
+ return getFlow(connectorId, managedProcessGroupId);
}
@Override
- public ProcessGroupFlowEntity getFlow(final String connectorId, final boolean uiOnly) throws NiFiClientException, IOException {
+ public ProcessGroupFlowEntity getFlow(final String connectorId, final String processGroupId) throws NiFiClientException, IOException {
Objects.requireNonNull(connectorId, "Connector ID required");
+ Objects.requireNonNull(processGroupId, "Process Group ID required");
return executeAction("Error retrieving connector flow", () -> {
WebTarget target = connectorTarget
- .path("/flow")
- .resolveTemplate("id", connectorId);
-
- if (uiOnly) {
- target = target.queryParam("uiOnly", "true");
- }
+ .path("/flow/process-groups/{processGroupId}")
+ .resolveTemplate("id", connectorId)
+ .resolveTemplate("processGroupId", processGroupId);
return getRequestBuilder(target).get(ProcessGroupFlowEntity.class);
});
(nifi) 35/40: NIFI-15482: MockConnectorAssetManager requires SPI file to start (#10783)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit cda2304051794f14a8854dcaa48c098255554b9a
Author: Bob Paulin <bo...@bobpaulin.com>
AuthorDate: Tue Jan 20 08:33:46 2026 -0600
NIFI-15482: MockConnectorAssetManager requires SPI file to start (#10783)
---
.../META-INF/services/org.apache.nifi.asset.AssetManager | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.asset.AssetManager b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.asset.AssetManager
new file mode 100644
index 0000000000..c23d0b1707
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.asset.AssetManager
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.nifi.mock.connector.server.MockConnectorAssetManager
(nifi) 07/40: NIFI-15322: Require all property descriptors within a PropertyGroup / ConfigurationStep are unique (#10626)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 9063b3ebc19db3c5761424a259d09fbba3a4c4b0
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Wed Dec 10 15:54:23 2025 -0500
NIFI-15322: Require all property descriptors within a PropertyGroup / ConfigurationStep are unique (#10626)
* NIFI-15322: Huge refactoring of Connectors to require that <Step Name, Property Name> tuple is unique. Requiring that is simple, but now that we enforce that, we can greatly simplify much
of the code.
* NIFI-15322: Added StepConfiguration to simplify API rather than using Map<String, ConnectorValueReference> in some places
* NIFI-15322: Now that we enforce unique property names per step, introduced StepConfigurationContext to further reduce API complexity
Signed-off-by: Kevin Doran <kd...@apache.org>
---
.../mock/connector/server/ConnectorTestRunner.java | 13 +-
.../server/StandardConnectorMockServer.java | 32 ++-
.../nifi/mock/connectors/GenerateAndLog.java | 4 +-
.../connector/StandardConnectorTestRunner.java | 20 +-
.../apache/nifi/connectors/kafkas3/KafkaToS3.java | 17 +-
.../connectors/kafkas3/KafkaToS3FlowBuilder.java | 64 +++---
.../nifi/connectors/kafkas3/KafkaToS3IT.java | 224 ++++++++++----------
.../flow/mapping/VersionedComponentFlowMapper.java | 23 +-
.../connector/ConnectorConfiguration.java | 21 +-
.../nifi/components/connector/ConnectorNode.java | 10 +-
.../components/connector/ConnectorRepository.java | 2 +-
.../MutableConnectorConfigurationContext.java | 16 +-
...figuration.java => NamedStepConfiguration.java} | 4 +-
.../connector/DescribedValueProvider.java | 2 +-
.../nifi/components/connector/GhostConnector.java | 7 +-
.../StandardConnectorConfigurationContext.java | 222 ++++++++------------
.../connector/StandardConnectorNode.java | 103 +++++----
.../connector/StandardConnectorRepository.java | 4 +-
.../StandardConnectorValidationContext.java | 42 ++--
.../StandardStepConfigurationContext.java | 59 ++++++
.../apache/nifi/controller/ExtensionBuilder.java | 68 +++---
.../serialization/VersionedFlowSynchronizer.java | 54 +----
.../components/connector/BlockingConnector.java | 7 +-
.../connector/DynamicAllowableValuesConnector.java | 6 +-
.../components/connector/DynamicFlowConnector.java | 11 +-
.../components/connector/ParameterConnector.java | 7 +-
.../components/connector/SleepingConnector.java | 7 +-
.../connector/StandardConnectorNodeIT.java | 44 ++--
.../TestStandardConnectorConfigurationContext.java | 233 +++++++--------------
.../connector/TestStandardConnectorNode.java | 69 +++---
.../apache/nifi/controller/flow/NopConnector.java | 8 +-
.../java/org/apache/nifi/nar/DummyConnector.java | 8 +-
32 files changed, 661 insertions(+), 750 deletions(-)
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
index 798f6d4676..8dc3730229 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
@@ -19,23 +19,24 @@ package org.apache.nifi.mock.connector.server;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.FlowUpdateException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.StepConfiguration;
import java.io.Closeable;
import java.time.Duration;
import java.util.List;
+import java.util.Map;
public interface ConnectorTestRunner extends Closeable {
void applyUpdate() throws FlowUpdateException;
- default void configure(String stepName, PropertyGroupConfiguration groupConfiguration) throws FlowUpdateException {
- configure(stepName, List.of(groupConfiguration));
- }
+ void configure(String stepName, StepConfiguration configuration) throws FlowUpdateException;
- void configure(String stepName, List<PropertyGroupConfiguration> groupConfigurations) throws FlowUpdateException;
+ void configure(String stepName, Map<String, String> propertyValues) throws FlowUpdateException;
- ConnectorConfigVerificationResult verifyConfiguration(String stepName, List<PropertyGroupConfiguration> groupConfigurations);
+ ConnectorConfigVerificationResult verifyConfiguration(String stepName, Map<String, String> propertyValueOverrides);
+
+ ConnectorConfigVerificationResult verifyConfiguration(String stepName, StepConfiguration configurationOverrides);
void startConnector();
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
index 54af7b0da6..590e5a8049 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
@@ -28,9 +28,11 @@ import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorState;
+import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.connector.StandaloneConnectorRequestReplicator;
+import org.apache.nifi.components.connector.StepConfiguration;
+import org.apache.nifi.components.connector.StringLiteralValue;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.components.validation.DisabledServiceValidationResult;
import org.apache.nifi.components.validation.ValidationState;
@@ -56,7 +58,9 @@ import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -200,13 +204,31 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
}
@Override
- public void configure(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) throws FlowUpdateException {
- connectorNode.setConfiguration(stepName, groupConfigurations);
+ public void configure(final String stepName, final StepConfiguration configuration) throws FlowUpdateException {
+ connectorNode.setConfiguration(stepName, configuration);
}
@Override
- public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) {
- final List<ConfigVerificationResult> results = connectorNode.verifyConfigurationStep(stepName, groupConfigurations);
+ public void configure(final String stepName, final Map<String, String> propertyValues) throws FlowUpdateException {
+ final StepConfiguration configuration = toStringLiteralConfiguration(propertyValues);
+ configure(stepName, configuration);
+ }
+
+ @Override
+ public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final Map<String, String> propertyValueOverrides) {
+ final StepConfiguration configuration = toStringLiteralConfiguration(propertyValueOverrides);
+ return verifyConfiguration(stepName, configuration);
+ }
+
+ private StepConfiguration toStringLiteralConfiguration(final Map<String, String> propertyValues) {
+ final Map<String, ConnectorValueReference> references = new HashMap<>(propertyValues.size());
+ propertyValues.forEach((key, value) -> references.put(key, new StringLiteralValue(value)));
+ return new StepConfiguration(references);
+ }
+
+ @Override
+ public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final StepConfiguration configurationOverrides) {
+ final List<ConfigVerificationResult> results = connectorNode.verifyConfigurationStep(stepName, configurationOverrides);
return new MockServerConfigVerificationResult(results);
}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
index 617a1ca055..f9d5aebffe 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
@@ -21,12 +21,12 @@ import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.connector.AbstractConnector;
import org.apache.nifi.components.connector.ConfigurationStep;
import org.apache.nifi.components.connector.FlowUpdateException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.components.connector.util.VersionedFlowUtils;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
+import java.util.Map;
public class GenerateAndLog extends AbstractConnector {
@@ -49,7 +49,7 @@ public class GenerateAndLog extends AbstractConnector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext workingContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext workingContext) {
return List.of();
}
}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
index 907204d0f0..5cf5d5ef70 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
@@ -21,7 +21,7 @@ import org.apache.nifi.NiFiServer;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.FlowUpdateException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
import org.apache.nifi.mock.connector.server.ConnectorMockServer;
import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
@@ -122,13 +122,23 @@ public class StandardConnectorTestRunner implements ConnectorTestRunner, Closeab
}
@Override
- public void configure(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) throws FlowUpdateException {
- mockServer.configure(stepName, groupConfigurations);
+ public void configure(final String stepName, final StepConfiguration configuration) throws FlowUpdateException {
+ mockServer.configure(stepName, configuration);
}
@Override
- public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) {
- return mockServer.verifyConfiguration(stepName, groupConfigurations);
+ public void configure(final String stepName, final Map<String, String> propertyValues) throws FlowUpdateException {
+ mockServer.configure(stepName, propertyValues);
+ }
+
+ @Override
+ public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final Map<String, String> propertyValueOverrides) {
+ return mockServer.verifyConfiguration(stepName, propertyValueOverrides);
+ }
+
+ @Override
+ public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final StepConfiguration configurationOverrides) {
+ return mockServer.verifyConfiguration(stepName, configurationOverrides);
}
@Override
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java
index c94226e132..43bf138845 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java
@@ -27,7 +27,6 @@ import org.apache.nifi.components.connector.ConfigurationStep;
import org.apache.nifi.components.connector.ConnectorConfigurationContext;
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.components.connector.InvocationFailedException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.connector.components.ControllerServiceFacade;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.components.connector.components.ProcessGroupFacade;
@@ -68,9 +67,9 @@ public class KafkaToS3 extends AbstractConnector {
@Override
public void prepareForUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
final String activeS3DataFormat = activeContext.getConfigurationContext().getProperty(
- S3Step.S3_STEP_NAME, S3Step.S3_DESTINATION_GROUP.getName(), S3Step.S3_DATA_FORMAT.getName()).getValue();
+ S3Step.S3_STEP_NAME, S3Step.S3_DATA_FORMAT.getName()).getValue();
final String workingS3DataFormat = workingContext.getConfigurationContext().getProperty(
- S3Step.S3_STEP_NAME, S3Step.S3_DESTINATION_GROUP.getName(), S3Step.S3_DATA_FORMAT.getName()).getValue();
+ S3Step.S3_STEP_NAME, S3Step.S3_DATA_FORMAT.getName()).getValue();
if (!activeS3DataFormat.equals(workingS3DataFormat)) {
getLogger().info("S3 Data Format changed from {} to {}; draining flow before updating it", activeS3DataFormat, workingS3DataFormat);
@@ -117,9 +116,9 @@ public class KafkaToS3 extends AbstractConnector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> propertyOverrides, final FlowContext workingFlowContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> propertyValueOverrides, final FlowContext workingFlowContext) {
// Get the current ConfigurationContext and then create a new one that contains the provided property values
- final ConnectorConfigurationContext configurationContext = workingFlowContext.getConfigurationContext().createWithOverrides(stepName, propertyOverrides);
+ final ConnectorConfigurationContext configurationContext = workingFlowContext.getConfigurationContext().createWithOverrides(stepName, propertyValueOverrides);
final VersionedExternalFlow flow = buildFlow(configurationContext);
// Validate Connectivity
@@ -191,7 +190,7 @@ public class KafkaToS3 extends AbstractConnector {
final Set<String> topicNames = new HashSet<>(topicsAvailable);
final List<String> specifiedTopics = workingFlowContext.getConfigurationContext().getProperty(KafkaTopicsStep.STEP_NAME,
- KafkaTopicsStep.KAFKA_TOPICS_GROUP.getName(), KafkaTopicsStep.TOPIC_NAMES.getName()).asList();
+ KafkaTopicsStep.TOPIC_NAMES.getName()).asList();
final String missingTopics = specifiedTopics.stream()
.filter(topic -> !topicNames.contains(topic))
.collect(Collectors.joining(", "));
@@ -235,14 +234,14 @@ public class KafkaToS3 extends AbstractConnector {
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext flowContext) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext flowContext) {
if (stepName.equals(KafkaTopicsStep.STEP_NAME) && propertyName.equals(KafkaTopicsStep.TOPIC_NAMES.getName())) {
return createAllowableValues(getAvailableTopics(flowContext));
} else if (stepName.equals(S3Step.S3_STEP_NAME) && propertyName.equals(S3Step.S3_REGION.getName())) {
return createAllowableValues(getPossibleS3Regions(flowContext));
}
- return super.fetchAllowableValues(stepName, groupName, propertyName, flowContext);
+ return super.fetchAllowableValues(stepName, propertyName, flowContext);
}
private List<AllowableValue> createAllowableValues(final List<String> values) {
@@ -257,7 +256,7 @@ public class KafkaToS3 extends AbstractConnector {
private List<String> getAvailableTopics(final FlowContext flowContext) {
// If Kafka Brokers not yet set, return empty list
final ConnectorConfigurationContext config = flowContext.getConfigurationContext();
- if (!config.getProperty(KafkaConnectionStep.KAFKA_CONNECTION_STEP, KafkaConnectionStep.KAFKA_SERVER_GROUP, KafkaConnectionStep.KAFKA_BROKERS).isSet()) {
+ if (!config.getProperty(KafkaConnectionStep.KAFKA_CONNECTION_STEP, KafkaConnectionStep.KAFKA_BROKERS).isSet()) {
return List.of();
}
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3FlowBuilder.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3FlowBuilder.java
index 88b22df5e3..e7d5a75c33 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3FlowBuilder.java
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3FlowBuilder.java
@@ -17,9 +17,8 @@
package org.apache.nifi.connectors.kafkas3;
-import org.apache.nifi.components.connector.ConfigurationStep;
import org.apache.nifi.components.connector.ConnectorConfigurationContext;
-import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.StepConfigurationContext;
import org.apache.nifi.components.connector.util.VersionedFlowUtils;
import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedExternalFlow;
@@ -56,9 +55,7 @@ public class KafkaToS3FlowBuilder {
}
private void configureSchemaRegistry(final VersionedExternalFlow externalFlow) {
- final String schemaRegistryUrl = configContext.getProperty(KafkaConnectionStep.KAFKA_CONNECTION_STEP,
- KafkaConnectionStep.SCHEMA_REGISTRY_GROUP,
- KafkaConnectionStep.SCHEMA_REGISTRY_URL).getValue();
+ final String schemaRegistryUrl = configContext.getProperty(KafkaConnectionStep.KAFKA_CONNECTION_STEP, KafkaConnectionStep.SCHEMA_REGISTRY_URL).getValue();
if (schemaRegistryUrl == null) {
final VersionedProcessGroup processGroup = externalFlow.getFlowContents();
@@ -87,10 +84,9 @@ public class KafkaToS3FlowBuilder {
}
private void updateSchemaRegistryParameters(final VersionedExternalFlow externalFlow) {
- final ConfigurationStep connectionStep = KafkaConnectionStep.KAFKA_CONNECTION_STEP;
- final ConnectorPropertyGroup registryGroup = KafkaConnectionStep.SCHEMA_REGISTRY_GROUP;
+ final StepConfigurationContext stepContext = configContext.scopedToStep(KafkaConnectionStep.KAFKA_CONNECTION_STEP);
- final String schemaRegistryUrl = configContext.getProperty(connectionStep, registryGroup, KafkaConnectionStep.SCHEMA_REGISTRY_URL).getValue();
+ final String schemaRegistryUrl = stepContext.getProperty(KafkaConnectionStep.SCHEMA_REGISTRY_URL).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Schema Registry URLs", schemaRegistryUrl);
if (schemaRegistryUrl == null) {
@@ -104,8 +100,8 @@ public class KafkaToS3FlowBuilder {
.findFirst()
.ifPresent(service -> service.setProperties(properties));
} else {
- final String username = configContext.getProperty(connectionStep, registryGroup, KafkaConnectionStep.SCHEMA_REGISTRY_USERNAME).getValue();
- final String password = configContext.getProperty(connectionStep, registryGroup, KafkaConnectionStep.SCHEMA_REGISTRY_PASSWORD).getValue();
+ final String username = stepContext.getProperty(KafkaConnectionStep.SCHEMA_REGISTRY_USERNAME).getValue();
+ final String password = stepContext.getProperty(KafkaConnectionStep.SCHEMA_REGISTRY_PASSWORD).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Schema Registry Username", username);
VersionedFlowUtils.setParameterValue(externalFlow, "Schema Registry Password", password);
@@ -116,23 +112,22 @@ public class KafkaToS3FlowBuilder {
}
private void updateKafkaConnectionParameters(final VersionedExternalFlow externalFlow) {
- final ConfigurationStep connectionStep = KafkaConnectionStep.KAFKA_CONNECTION_STEP;
- final ConnectorPropertyGroup serverGroup = KafkaConnectionStep.KAFKA_SERVER_GROUP;
+ final StepConfigurationContext stepContext = configContext.scopedToStep(KafkaConnectionStep.KAFKA_CONNECTION_STEP);
- final String kafkaBrokers = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.KAFKA_BROKERS).getValue();
+ final String kafkaBrokers = stepContext.getProperty(KafkaConnectionStep.KAFKA_BROKERS).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Kafka Bootstrap Servers", kafkaBrokers);
- final String securityProtocol = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.SECURITY_PROTOCOL).getValue();
+ final String securityProtocol = stepContext.getProperty(KafkaConnectionStep.SECURITY_PROTOCOL).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Kafka Security Protocol", securityProtocol);
if (securityProtocol.contains("SASL")) {
- final String saslMechanism = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.SASL_MECHANISM).getValue();
+ final String saslMechanism = stepContext.getProperty(KafkaConnectionStep.SASL_MECHANISM).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Kafka SASL Mechanism", saslMechanism);
- final String username = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.USERNAME).getValue();
+ final String username = stepContext.getProperty(KafkaConnectionStep.USERNAME).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Kafka SASL Username", username);
- final String password = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.PASSWORD).getValue();
+ final String password = stepContext.getProperty(KafkaConnectionStep.PASSWORD).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Kafka SASL Password", password);
}
}
@@ -145,7 +140,7 @@ public class KafkaToS3FlowBuilder {
.findFirst()
.orElseThrow();
- final String kafkaDataFormat = configContext.getProperty(KafkaTopicsStep.STEP_NAME, KafkaTopicsStep.KAFKA_TOPICS_GROUP.getName(), KafkaTopicsStep.KAFKA_DATA_FORMAT.getName()).getValue();
+ final String kafkaDataFormat = configContext.getProperty(KafkaTopicsStep.STEP_NAME, KafkaTopicsStep.KAFKA_DATA_FORMAT.getName()).getValue();
if (!kafkaDataFormat.equalsIgnoreCase("JSON")) {
// Update ConsumeKafka processor to use Avro Reader
rootGroup.getProcessors().stream()
@@ -156,38 +151,39 @@ public class KafkaToS3FlowBuilder {
VersionedFlowUtils.setParameterValue(externalFlow, "Kafka Data Format", kafkaDataFormat);
- // TODO: Have this throw an Exception if GROUP does not contain the given property
- final String s3DataFormat = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_DATA_FORMAT).getValue();
+ final String s3DataFormat = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DATA_FORMAT).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "S3 Data Format", s3DataFormat);
}
private void updateKafkaTopicsParameters(final VersionedExternalFlow externalFlow) {
- final String groupName = KafkaTopicsStep.KAFKA_TOPICS_GROUP.getName();
+ final StepConfigurationContext stepContext = configContext.scopedToStep(KafkaTopicsStep.STEP_NAME);
- final String topics = configContext.getProperty(KafkaTopicsStep.STEP_NAME, groupName, KafkaTopicsStep.TOPIC_NAMES.getName()).getValue();
+ final String topics = stepContext.getProperty(KafkaTopicsStep.TOPIC_NAMES.getName()).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Topic Names", topics);
- final String groupId = configContext.getProperty(KafkaTopicsStep.STEP_NAME, groupName, KafkaTopicsStep.CONSUMER_GROUP_ID.getName()).getValue();
+ final String groupId = stepContext.getProperty(KafkaTopicsStep.CONSUMER_GROUP_ID.getName()).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Consumer Group ID", groupId);
- final String offsetReset = configContext.getProperty(KafkaTopicsStep.STEP_NAME, groupName, KafkaTopicsStep.OFFSET_RESET.getName()).getValue();
+ final String offsetReset = stepContext.getProperty(KafkaTopicsStep.OFFSET_RESET.getName()).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Kafka Auto Offset Reset", offsetReset);
}
private void updateS3Config(final VersionedExternalFlow externalFlow) {
- final String region = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_REGION).getValue();
+ final StepConfigurationContext stepContext = configContext.scopedToStep(S3Step.S3_STEP);
+
+ final String region = stepContext.getProperty(S3Step.S3_REGION).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "S3 Region", region);
- final String bucket = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_BUCKET).getValue();
+ final String bucket = stepContext.getProperty(S3Step.S3_BUCKET).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "S3 Bucket", bucket);
- final String prefix = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_PREFIX).getValue();
+ final String prefix = stepContext.getProperty(S3Step.S3_PREFIX).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "S3 Prefix", prefix);
- final String endpointOverrideUrl = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_ENDPOINT_OVERRIDE_URL).getValue();
+ final String endpointOverrideUrl = stepContext.getProperty(S3Step.S3_ENDPOINT_OVERRIDE_URL).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "S3 Endpoint Override URL", endpointOverrideUrl);
- final String authStrategy = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_CREDENTIALS_GROUP, S3Step.S3_AUTHENTICATION_STRATEGY).getValue();
+ final String authStrategy = stepContext.getProperty(S3Step.S3_AUTHENTICATION_STRATEGY).getValue();
if (authStrategy.equals(S3Step.DEFAULT_CREDENTIALS)) {
final VersionedControllerService credentialsService = externalFlow.getFlowContents().getControllerServices().stream()
.filter(service -> service.getType().endsWith("AWSCredentialsProviderControllerService"))
@@ -196,22 +192,22 @@ public class KafkaToS3FlowBuilder {
credentialsService.setProperties(Map.of("default-credentials", "true"));
} else {
- final String accessKey = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_CREDENTIALS_GROUP, S3Step.S3_ACCESS_KEY_ID).getValue();
+ final String accessKey = stepContext.getProperty(S3Step.S3_ACCESS_KEY_ID).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "S3 Access Key ID", accessKey);
- final String secretKey = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_CREDENTIALS_GROUP, S3Step.S3_SECRET_ACCESS_KEY).getValue();
+ final String secretKey = stepContext.getProperty(S3Step.S3_SECRET_ACCESS_KEY).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "S3 Secret Access Key", secretKey);
}
- final long mergeBytes = configContext.getProperty(S3Step.S3_STEP, S3Step.MERGE_GROUP, S3Step.TARGET_OBJECT_SIZE).asDataSize(DataUnit.B).longValue();
- final String mergeSize = configContext.getProperty(S3Step.S3_STEP, S3Step.MERGE_GROUP, S3Step.TARGET_OBJECT_SIZE).getValue();
+ final long mergeBytes = stepContext.getProperty(S3Step.TARGET_OBJECT_SIZE).asDataSize(DataUnit.B).longValue();
+ final String mergeSize = stepContext.getProperty(S3Step.TARGET_OBJECT_SIZE).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Target Object Size", mergeSize);
// Max Bin size will be either 10% more than target size or target size + 100MB, whichever is smaller
final long maxBinSize = (long) Math.min(mergeBytes + 100_000_000, mergeBytes * 1.1D);
VersionedFlowUtils.setParameterValue(externalFlow, "Maximum Object Size", maxBinSize + " B");
- final String mergeLatency = configContext.getProperty(S3Step.S3_STEP, S3Step.MERGE_GROUP, S3Step.MERGE_LATENCY).getValue();
+ final String mergeLatency = stepContext.getProperty(S3Step.MERGE_LATENCY).getValue();
VersionedFlowUtils.setParameterValue(externalFlow, "Merge Latency", mergeLatency);
}
}
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java
index 77b748c74e..6ccdc34fd9 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java
@@ -33,7 +33,6 @@ import org.apache.kafka.common.serialization.StringSerializer;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.FlowUpdateException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
@@ -296,42 +295,42 @@ public class KafkaToS3IT {
"Final plaintext record"
);
- final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ final Map<String, String> kafkaServerConfig = Map.of(
"Kafka Brokers", "localhost:9093",
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
"Username", SCRAM_USERNAME,
"Password", SCRAM_PASSWORD
- ));
+ );
runner.applyUpdate();
// Perform verification to ensure that valid server configuration passes
- final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", List.of(kafkaServerConfig));
+ final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", kafkaServerConfig);
connectionVerificationResults.assertNoFailures();
// Apply the configuration that we've now validated
- runner.configure("Kafka Connection", List.of(kafkaServerConfig));
+ runner.configure("Kafka Connection", kafkaServerConfig);
// Perform verification to ensure that valid topic configuration passes
- final PropertyGroupConfiguration topic1Config = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ final Map<String, String> topic1Config = Map.of(
"Topic Names", "topic-1",
"Consumer Group ID", "nifi-kafka-to-s3-testSuccessfulFlow",
"Offset Reset", "earliest",
"Kafka Data Format", "JSON"
- ));
- final ConnectorConfigVerificationResult topic1VerificationResults = runner.verifyConfiguration("Kafka Topics", List.of(topic1Config));
+ );
+ final ConnectorConfigVerificationResult topic1VerificationResults = runner.verifyConfiguration("Kafka Topics", topic1Config);
topic1VerificationResults.assertNoFailures();
// Perform verification against a topic with invalid data for the selected data format
- final PropertyGroupConfiguration importantTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ final Map<String, String> importantTopicConfig = Map.of(
"Topic Names", "an-important-topic",
"Consumer Group ID", "nifi-kafka-to-s3-testSuccessfulFlow",
"Offset Reset", "earliest",
"Kafka Data Format", "JSON"
- ));
+ );
- final ConnectorConfigVerificationResult importantTopicVerificationResults = runner.verifyConfiguration("Kafka Topics", List.of(importantTopicConfig));
+ final ConnectorConfigVerificationResult importantTopicVerificationResults = runner.verifyConfiguration("Kafka Topics", importantTopicConfig);
final List<ConfigVerificationResult> invalidImportantTopicResults = importantTopicVerificationResults.getFailedResults();
assertEquals(1, invalidImportantTopicResults.size());
final ConfigVerificationResult invalidResult = invalidImportantTopicResults.getFirst();
@@ -357,40 +356,36 @@ public class KafkaToS3IT {
{"page": 4, "words": "The end." }"""
);
- final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ final Map<String, String> kafkaServerConfig = Map.of(
"Kafka Brokers", "localhost:9093",
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
"Username", SCRAM_USERNAME,
"Password", SCRAM_PASSWORD
- ));
+ );
- final PropertyGroupConfiguration kafkaTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ final Map<String, String> kafkaTopicConfig = Map.of(
"Topic Names", "story",
"Consumer Group ID", "nifi-kafka-to-s3-testSuccessfulFlow",
"Offset Reset", "earliest",
"Kafka Data Format", "JSON"
- ));
+ );
- final PropertyGroupConfiguration s3DestinationConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
- "S3 Region", S3_REGION,
- "S3 Data Format", "Avro",
- "S3 Bucket", bucketName,
- "S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()
- ));
- final PropertyGroupConfiguration s3CredentialsConfig = PropertyGroupConfiguration.fromStringValues("S3 Credentials", Map.of(
- "S3 Authentication Strategy", "Access Key ID and Secret Key",
- "S3 Access Key ID", localStackContainer.getAccessKey(),
- "S3 Secret Access Key", localStackContainer.getSecretKey()
- ));
- final PropertyGroupConfiguration s3MergeConfig = PropertyGroupConfiguration.fromStringValues("Merge Configuration", Map.of(
- "Target Object Size", "1 MB",
- "Merge Latency", "1 sec"
- ));
+ final Map<String, String> s3Config = Map.ofEntries(
+ Map.entry("S3 Region", S3_REGION),
+ Map.entry("S3 Data Format", "Avro"),
+ Map.entry("S3 Bucket", bucketName),
+ Map.entry("S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()),
+ Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
+ Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
+ Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
+ Map.entry("Target Object Size", "1 MB"),
+ Map.entry("Merge Latency", "1 sec")
+ );
- runner.configure("Kafka Connection", List.of(kafkaServerConfig));
- runner.configure("Kafka Topics", List.of(kafkaTopicConfig));
- runner.configure("S3 Configuration", List.of(s3DestinationConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.configure("Kafka Connection", kafkaServerConfig);
+ runner.configure("Kafka Topics", kafkaTopicConfig);
+ runner.configure("S3 Configuration", s3Config);
runner.applyUpdate();
final List<ValidationResult> validationResults = runner.validate();
@@ -449,31 +444,28 @@ public class KafkaToS3IT {
produceAvroRecordsToTopic("avro-topic", schema, record1, record2);
- final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ final Map<String, String> kafkaConnectionConfig = Map.of(
"Kafka Brokers", "localhost:9093",
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
"Username", SCRAM_USERNAME,
- "Password", SCRAM_PASSWORD
- ));
-
- final PropertyGroupConfiguration schemaRegistryConfig = PropertyGroupConfiguration.fromStringValues("Schema Registry Settings", Map.of(
+ "Password", SCRAM_PASSWORD,
"Schema Registry URL", getSchemaRegistryUrl()
- ));
+ );
- final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", List.of(kafkaServerConfig, schemaRegistryConfig));
+ final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", kafkaConnectionConfig);
connectionVerificationResults.assertNoFailures();
- runner.configure("Kafka Connection", List.of(kafkaServerConfig, schemaRegistryConfig));
+ runner.configure("Kafka Connection", kafkaConnectionConfig);
- final PropertyGroupConfiguration avroTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ final Map<String, String> avroTopicConfig = Map.of(
"Topic Names", "avro-topic",
"Consumer Group ID", "nifi-kafka-to-s3-testSchemaRegistryVerification",
"Offset Reset", "earliest",
"Kafka Data Format", "Avro"
- ));
+ );
- final ConnectorConfigVerificationResult avroTopicVerificationResults = runner.verifyConfiguration("Kafka Topics", List.of(avroTopicConfig));
+ final ConnectorConfigVerificationResult avroTopicVerificationResults = runner.verifyConfiguration("Kafka Topics", avroTopicConfig);
avroTopicVerificationResults.assertNoFailures();
runner.applyUpdate();
@@ -520,44 +512,37 @@ public class KafkaToS3IT {
produceAvroRecordsToTopic("user-events", schema, record1, record2, record3);
- final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ final Map<String, String> kafkaConnectionConfig = Map.of(
"Kafka Brokers", "localhost:9093",
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
"Username", SCRAM_USERNAME,
- "Password", SCRAM_PASSWORD
- ));
-
- final PropertyGroupConfiguration schemaRegistryConfig = PropertyGroupConfiguration.fromStringValues("Schema Registry Settings", Map.of(
+ "Password", SCRAM_PASSWORD,
"Schema Registry URL", getSchemaRegistryUrl()
- ));
+ );
- final PropertyGroupConfiguration kafkaTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ final Map<String, String> kafkaTopicConfig = Map.of(
"Topic Names", "user-events",
"Consumer Group ID", "nifi-kafka-to-s3-testSchemaRegistry",
"Offset Reset", "earliest",
"Kafka Data Format", "Avro"
- ));
+ );
- final PropertyGroupConfiguration s3DestinationConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
- "S3 Region", S3_REGION,
- "S3 Data Format", "Avro",
- "S3 Bucket", bucketName,
- "S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()
- ));
- final PropertyGroupConfiguration s3CredentialsConfig = PropertyGroupConfiguration.fromStringValues("S3 Credentials", Map.of(
- "S3 Authentication Strategy", "Access Key ID and Secret Key",
- "S3 Access Key ID", localStackContainer.getAccessKey(),
- "S3 Secret Access Key", localStackContainer.getSecretKey()
- ));
- final PropertyGroupConfiguration s3MergeConfig = PropertyGroupConfiguration.fromStringValues("Merge Configuration", Map.of(
- "Target Object Size", "1 MB",
- "Merge Latency", "1 sec"
- ));
+ final Map<String, String> s3Config = Map.ofEntries(
+ Map.entry("S3 Region", S3_REGION),
+ Map.entry("S3 Data Format", "Avro"),
+ Map.entry("S3 Bucket", bucketName),
+ Map.entry("S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()),
+ Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
+ Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
+ Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
+ Map.entry("Target Object Size", "1 MB"),
+ Map.entry("Merge Latency", "1 sec")
+ );
- runner.configure("Kafka Connection", List.of(kafkaServerConfig, schemaRegistryConfig));
- runner.configure("Kafka Topics", List.of(kafkaTopicConfig));
- runner.configure("S3 Configuration", List.of(s3DestinationConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.configure("Kafka Connection", kafkaConnectionConfig);
+ runner.configure("Kafka Topics", kafkaTopicConfig);
+ runner.configure("S3 Configuration", s3Config);
runner.applyUpdate();
final List<ValidationResult> validationResults = runner.validate();
@@ -624,44 +609,36 @@ public class KafkaToS3IT {
// Configure Connector to consume from JSON Kafka topic and write to S3 in JSON format, but with an invalid S3 endpoint.
// This will cause the data to remain queued, since PutS3Object will fail to write the data.
- final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ final Map<String, String> kafkaServerConfig = Map.of(
"Kafka Brokers", "localhost:9093",
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
"Username", SCRAM_USERNAME,
"Password", SCRAM_PASSWORD
- ));
-
- final PropertyGroupConfiguration schemaRegistryConfig = PropertyGroupConfiguration.fromStringValues("Schema Registry Settings", Map.of(
- "Schema Registry URL", getSchemaRegistryUrl()
- ));
+ );
- final PropertyGroupConfiguration jsonTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ final Map<String, String> jsonTopicConfig = Map.of(
"Topic Names", "json",
"Consumer Group ID", "nifi-kafka-to-s3-testReconfiguration",
"Offset Reset", "earliest",
"Kafka Data Format", "JSON"
- ));
+ );
- final PropertyGroupConfiguration s3InvalidDestinationConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
- "S3 Region", S3_REGION,
- "S3 Data Format", "JSON",
- "S3 Bucket", bucketName,
- "S3 Endpoint Override URL", "http://invalid-s3-endpoint:9999"
- ));
- final PropertyGroupConfiguration s3CredentialsConfig = PropertyGroupConfiguration.fromStringValues("S3 Credentials", Map.of(
- "S3 Authentication Strategy", "Access Key ID and Secret Key",
- "S3 Access Key ID", localStackContainer.getAccessKey(),
- "S3 Secret Access Key", localStackContainer.getSecretKey()
- ));
- final PropertyGroupConfiguration s3MergeConfig = PropertyGroupConfiguration.fromStringValues("Merge Configuration", Map.of(
- "Target Object Size", "1 MB",
- "Merge Latency", "1 sec"
- ));
+ final Map<String, String> s3InvalidConfig = Map.ofEntries(
+ Map.entry("S3 Region", S3_REGION),
+ Map.entry("S3 Data Format", "JSON"),
+ Map.entry("S3 Bucket", bucketName),
+ Map.entry("S3 Endpoint Override URL", "http://invalid-s3-endpoint:9999"),
+ Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
+ Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
+ Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
+ Map.entry("Target Object Size", "1 MB"),
+ Map.entry("Merge Latency", "1 sec")
+ );
- runner.configure("Kafka Connection", List.of(kafkaServerConfig));
- runner.configure("Kafka Topics", List.of(jsonTopicConfig));
- runner.configure("S3 Configuration", List.of(s3InvalidDestinationConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.configure("Kafka Connection", kafkaServerConfig);
+ runner.configure("Kafka Topics", jsonTopicConfig);
+ runner.configure("S3 Configuration", s3InvalidConfig);
runner.applyUpdate();
// Run the Connector with the invalid S3 endpoint to queue the JSON data. Wait for data to be queued up.
@@ -674,14 +651,19 @@ public class KafkaToS3IT {
// Apply configuration to specify the correct S3 endpoint. Keep S3 Data Format as JSON for now.
// This will allow the Connector to write data to S3 and properly drain the data when we switch the S3 format from JSON to Avro.
- final PropertyGroupConfiguration s3ValidDestinationJsonConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
- "S3 Region", S3_REGION,
- "S3 Data Format", "JSON",
- "S3 Bucket", bucketName,
- "S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()
- ));
+ final Map<String, String> s3ValidJsonConfig = Map.ofEntries(
+ Map.entry("S3 Region", S3_REGION),
+ Map.entry("S3 Data Format", "JSON"),
+ Map.entry("S3 Bucket", bucketName),
+ Map.entry("S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()),
+ Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
+ Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
+ Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
+ Map.entry("Target Object Size", "1 MB"),
+ Map.entry("Merge Latency", "1 sec")
+ );
- runner.configure("S3 Configuration", List.of(s3ValidDestinationJsonConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.configure("S3 Configuration", s3ValidJsonConfig);
runner.applyUpdate();
// Make sure there is no data in S3 yet.
@@ -690,24 +672,38 @@ public class KafkaToS3IT {
assertEquals(List.of(), initialS3Objects);
// Now change the S3 Data Format from JSON to Avro. This should trigger draining of the queued JSON data.
- final PropertyGroupConfiguration s3ValidDestinationAvroConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
- "S3 Region", S3_REGION,
- "S3 Data Format", "Avro",
- "S3 Bucket", bucketName,
- "S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()
- ));
+ final Map<String, String> s3ValidAvroConfig = Map.ofEntries(
+ Map.entry("S3 Region", S3_REGION),
+ Map.entry("S3 Data Format", "Avro"),
+ Map.entry("S3 Bucket", bucketName),
+ Map.entry("S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()),
+ Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
+ Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
+ Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
+ Map.entry("Target Object Size", "1 MB"),
+ Map.entry("Merge Latency", "1 sec")
+ );
// Configure to consume from the Avro topic and change S3 format to Avro
- final PropertyGroupConfiguration avroTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ final Map<String, String> avroTopicConfig = Map.of(
"Topic Names", "avro",
"Consumer Group ID", "nifi-kafka-to-s3-testReconfiguration",
"Offset Reset", "earliest",
"Kafka Data Format", "Avro"
- ));
+ );
+
+ final Map<String, String> kafkaConnectionWithSchemaRegistry = Map.of(
+ "Kafka Brokers", "localhost:9093",
+ "Security Protocol", "SASL_PLAINTEXT",
+ "SASL Mechanism", "PLAIN",
+ "Username", SCRAM_USERNAME,
+ "Password", SCRAM_PASSWORD,
+ "Schema Registry URL", getSchemaRegistryUrl()
+ );
- runner.configure("Kafka Connection", List.of(kafkaServerConfig, schemaRegistryConfig));
- runner.configure("Kafka Topics", List.of(avroTopicConfig));
- runner.configure("S3 Configuration", List.of(s3ValidDestinationAvroConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.configure("Kafka Connection", kafkaConnectionWithSchemaRegistry);
+ runner.configure("Kafka Topics", avroTopicConfig);
+ runner.configure("S3 Configuration", s3ValidAvroConfig);
runner.applyUpdate();
// After draining, there should be one JSON file in S3.
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
index 365b49f713..e245dc68bf 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/mapping/VersionedComponentFlowMapper.java
@@ -23,14 +23,14 @@ import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.listen.ListenPortDefinition;
import org.apache.nifi.components.connector.AssetReference;
-import org.apache.nifi.components.connector.ConfigurationStepConfiguration;
+import org.apache.nifi.components.connector.NamedStepConfiguration;
import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FrameworkFlowContext;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
import org.apache.nifi.components.resource.ResourceCardinality;
import org.apache.nifi.components.resource.ResourceDefinition;
@@ -66,7 +66,6 @@ import org.apache.nifi.flow.VersionedAsset;
import org.apache.nifi.flow.VersionedConfigurationStep;
import org.apache.nifi.flow.VersionedConnection;
import org.apache.nifi.flow.VersionedConnector;
-import org.apache.nifi.flow.VersionedConnectorPropertyGroup;
import org.apache.nifi.flow.VersionedConnectorValueReference;
import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedFlowAnalysisRule;
@@ -1062,28 +1061,20 @@ public class VersionedComponentFlowMapper {
final ConnectorConfiguration configuration = flowContext.getConfigurationContext().toConnectorConfiguration();
final List<VersionedConfigurationStep> configurationSteps = new ArrayList<>();
- for (final ConfigurationStepConfiguration stepConfiguration : configuration.getConfigurationStepConfigurations()) {
+ for (final NamedStepConfiguration stepConfiguration : configuration.getNamedStepConfigurations()) {
final VersionedConfigurationStep versionedConfigurationStep = new VersionedConfigurationStep();
versionedConfigurationStep.setName(stepConfiguration.stepName());
- configurationSteps.add(versionedConfigurationStep);
-
- final List<VersionedConnectorPropertyGroup> propertyGroups = new ArrayList<>();
- versionedConfigurationStep.setPropertyGroups(propertyGroups);
+ versionedConfigurationStep.setProperties(mapPropertyValues(stepConfiguration.configuration()));
- for (final PropertyGroupConfiguration groupConfiguration : stepConfiguration.propertyGroupConfigurations()) {
- final VersionedConnectorPropertyGroup versionedGroup = new VersionedConnectorPropertyGroup();
- versionedGroup.setName(groupConfiguration.groupName());
- versionedGroup.setProperties(mapPropertyValues(groupConfiguration.propertyValues()));
- propertyGroups.add(versionedGroup);
- }
+ configurationSteps.add(versionedConfigurationStep);
}
return configurationSteps;
}
- private Map<String, VersionedConnectorValueReference> mapPropertyValues(final Map<String, ConnectorValueReference> propertyValues) {
+ private Map<String, VersionedConnectorValueReference> mapPropertyValues(final StepConfiguration configuration) {
final Map<String, VersionedConnectorValueReference> versionedProperties = new HashMap<>();
- for (final Map.Entry<String, ConnectorValueReference> entry : propertyValues.entrySet()) {
+ for (final Map.Entry<String, ConnectorValueReference> entry : configuration.getPropertyValues().entrySet()) {
final ConnectorValueReference valueReference = entry.getValue();
final VersionedConnectorValueReference versionedReference = new VersionedConnectorValueReference();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorConfiguration.java
index f58c10a8e1..88c54d2998 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorConfiguration.java
@@ -16,21 +16,20 @@
*/
package org.apache.nifi.components.connector;
-import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class ConnectorConfiguration {
- private final Set<ConfigurationStepConfiguration> configurationStepConfigurations;
+ private final Set<NamedStepConfiguration> stepConfigurations;
- public ConnectorConfiguration(final Collection<ConfigurationStepConfiguration> configurationStepConfigurations) {
- this.configurationStepConfigurations = new HashSet<>(configurationStepConfigurations);
+ public ConnectorConfiguration(final Set<NamedStepConfiguration> stepConfigurations) {
+ this.stepConfigurations = new HashSet<>(stepConfigurations);
}
- public Set<ConfigurationStepConfiguration> getConfigurationStepConfigurations() {
- return configurationStepConfigurations;
+ public Set<NamedStepConfiguration> getNamedStepConfigurations() {
+ return stepConfigurations;
}
@Override
@@ -40,18 +39,18 @@ public class ConnectorConfiguration {
}
final ConnectorConfiguration that = (ConnectorConfiguration) other;
- return Objects.equals(configurationStepConfigurations, that.configurationStepConfigurations);
+ return Objects.equals(stepConfigurations, that.stepConfigurations);
}
@Override
public int hashCode() {
- return Objects.hashCode(configurationStepConfigurations);
+ return Objects.hashCode(stepConfigurations);
}
@Override
public String toString() {
- return "ConnectorConfiguration{" +
- "configurationStepConfigurations=" + configurationStepConfigurations +
- '}';
+ return "ConnectorConfiguration[" +
+ "stepConfigurations=" + stepConfigurations +
+ "]";
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index 2f9340ce7f..f61d35e3e0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -63,9 +63,9 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
BundleCoordinate getBundleCoordinate();
- List<AllowableValue> fetchAllowableValues(String stepName, String groupName, String propertyName);
+ List<AllowableValue> fetchAllowableValues(String stepName, String propertyName);
- List<AllowableValue> fetchAllowableValues(String stepName, String groupName, String propertyName, String filter);
+ List<AllowableValue> fetchAllowableValues(String stepName, String propertyName, String filter);
void initializeConnector(FrameworkConnectorInitializationContext initializationContext);
@@ -104,7 +104,7 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
void resumeValidationTrigger();
- List<ConfigVerificationResult> verifyConfigurationStep(String configurationStepName, List<PropertyGroupConfiguration> propertyGroupConfigurations);
+ List<ConfigVerificationResult> verifyConfigurationStep(String configurationStepName, StepConfiguration configurationOverrides);
List<ConfigVerificationResult> verify();
@@ -179,10 +179,10 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
* @param configurationStepName the name of the configuration step being set
* (must match one of the names returned by {@link Connector#getConfigurationSteps(FlowContext)})
* when providing the working flow context
- * @param propertyGroupConfigurations the list of PropertyGroupConfigurations for the given configuration step
+ * @param configuration the configuration for the given configuration step
* @throws FlowUpdateException if unable to apply the configuration changes
*/
- void setConfiguration(String configurationStepName, List<PropertyGroupConfiguration> propertyGroupConfigurations) throws FlowUpdateException;
+ void setConfiguration(String configurationStepName, StepConfiguration configuration) throws FlowUpdateException;
void prepareForUpdate() throws FlowUpdateException;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
index 0caa7158b9..3ea48b996a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
@@ -75,7 +75,7 @@ public interface ConnectorRepository {
*/
Future<Void> stopConnector(ConnectorNode connector);
- void configureConnector(ConnectorNode connector, String stepName, List<PropertyGroupConfiguration> stepConfiguration) throws FlowUpdateException;
+ void configureConnector(ConnectorNode connector, String stepName, StepConfiguration configuration) throws FlowUpdateException;
void applyUpdate(ConnectorNode connector) throws FlowUpdateException;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
index fd428b6882..aaf4c1fdf3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
@@ -17,29 +17,29 @@
package org.apache.nifi.components.connector;
-import java.util.List;
+import java.util.Map;
public interface MutableConnectorConfigurationContext extends ConnectorConfigurationContext {
/**
* Sets the properties for the given step to the provided properties. Any existing properties
- * for the step that are not included in the provided list will remain unchanged.
+ * for the step that are not included in the provided configuration will remain unchanged.
*
* @param stepName the name of the configuration step
- * @param propertyGroupConfigurations the property group configurations to set
+ * @param configuration the configuration to set
* @return the result of the configuration update
*/
- ConfigurationUpdateResult setProperties(String stepName, List<PropertyGroupConfiguration> propertyGroupConfigurations);
+ ConfigurationUpdateResult setProperties(String stepName, StepConfiguration configuration);
/**
* Replaces all of the properties for the given step with the provided properties. Any existing properties
- * for the step that are not included in the provided list will be removed.
+ * for the step that are not included in the provided configuration will be removed.
*
* @param stepName the name of the configuration step
- * @param propertyGroupConfigurations the property group configurations to set
+ * @param configuration the configuration to set
* @return the result of the configuration update
*/
- ConfigurationUpdateResult replaceProperties(String stepName, List<PropertyGroupConfiguration> propertyGroupConfigurations);
+ ConfigurationUpdateResult replaceProperties(String stepName, StepConfiguration configuration);
/**
* Converts this mutable configuration context to an immutable ConnectorConfiguration.
@@ -48,7 +48,7 @@ public interface MutableConnectorConfigurationContext extends ConnectorConfigura
ConnectorConfiguration toConnectorConfiguration();
@Override
- MutableConnectorConfigurationContext createWithOverrides(String stepName, List<PropertyGroupConfiguration> propertyGroupConfigurations);
+ MutableConnectorConfigurationContext createWithOverrides(String stepName, Map<String, String> propertyValues);
/**
* Creates a clone of this MutableConnectorConfigurationContext.
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConfigurationStepConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/NamedStepConfiguration.java
similarity index 85%
rename from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConfigurationStepConfiguration.java
rename to nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/NamedStepConfiguration.java
index eb3c568070..7c17798ac8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConfigurationStepConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/NamedStepConfiguration.java
@@ -17,7 +17,5 @@
package org.apache.nifi.components.connector;
-import java.util.List;
-
-public record ConfigurationStepConfiguration(String stepName, List<PropertyGroupConfiguration> propertyGroupConfigurations) {
+public record NamedStepConfiguration(String stepName, StepConfiguration configuration) {
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/DescribedValueProvider.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/DescribedValueProvider.java
index a80d7209bf..7b5004fac7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/DescribedValueProvider.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/DescribedValueProvider.java
@@ -23,6 +23,6 @@ import java.util.List;
public interface DescribedValueProvider {
- List<DescribedValue> fetchAllowableValues(String stepName, String groupName, String propertyName);
+ List<DescribedValue> fetchAllowableValues(String stepName, String propertyName);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
index d13f642ca2..f936c24446 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
@@ -25,6 +25,7 @@ import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
+import java.util.Map;
public class GhostConnector implements Connector {
private final String identifier;
@@ -102,7 +103,7 @@ public class GhostConnector implements Connector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext workingContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext workingContext) {
return configVerificationResults;
}
@@ -112,12 +113,12 @@ public class GhostConnector implements Connector {
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext workingContext, final String filter) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext, final String filter) {
return List.of();
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext workingContext) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext) {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
index 7ea8c6434d..6940112ad9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
@@ -21,8 +21,9 @@ import org.apache.nifi.asset.AssetManager;
import java.io.IOException;
import java.io.UncheckedIOException;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -38,8 +39,8 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
private final AssetManager assetManager;
private final SecretsManager secretsManager;
- final Map<String, List<PropertyGroupConfiguration>> propertyGroupConfigurations = new HashMap<>();
- final Map<String, List<PropertyGroupConfiguration>> resolvedPropertyGroupConfigurations = new HashMap<>();
+ final Map<String, StepConfiguration> propertyConfigurations = new HashMap<>();
+ final Map<String, StepConfiguration> resolvedPropertyConfigurations = new HashMap<>();
public StandardConnectorConfigurationContext(final AssetManager assetManager, final SecretsManager secretsManager) {
this.assetManager = assetManager;
@@ -47,33 +48,25 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
}
@Override
- public ConnectorPropertyValue getProperty(final String stepName, final String groupName, final String propertyName) {
- return getProperty(stepName, groupName, propertyName, null);
+ public ConnectorPropertyValue getProperty(final String stepName, final String propertyName) {
+ return getProperty(stepName, propertyName, null);
}
- private ConnectorPropertyValue getProperty(final String stepName, final String groupName, final String propertyName, final String defaultValue) {
+ private ConnectorPropertyValue getProperty(final String stepName, final String propertyName, final String defaultValue) {
readLock.lock();
try {
- final List<PropertyGroupConfiguration> groupConfigs = resolvedPropertyGroupConfigurations.get(stepName);
- if (groupConfigs == null) {
+ final StepConfiguration resolvedConfig = resolvedPropertyConfigurations.get(stepName);
+ if (resolvedConfig == null) {
return new StandardConnectorPropertyValue(defaultValue);
}
- for (final PropertyGroupConfiguration groupConfig : groupConfigs) {
- if (!Objects.equals(groupConfig.groupName(), groupName)) {
- continue;
- }
-
- final ConnectorValueReference valueReference = groupConfig.propertyValues().get(propertyName);
- if (valueReference == null) {
- return new StandardConnectorPropertyValue(defaultValue);
- }
-
- // The resolvedPropertyGroupConfigurations contains only StringLiteralValue references.
+ final ConnectorValueReference valueReference = resolvedConfig.getPropertyValue(propertyName);
+ if (valueReference != null) {
+ // The resolvedPropertyConfigurations contains only StringLiteralValue references.
return new StandardConnectorPropertyValue(((StringLiteralValue) valueReference).getValue());
}
- // Property Group not found
+ // Property not found
return new StandardConnectorPropertyValue(defaultValue);
} finally {
readLock.unlock();
@@ -81,46 +74,65 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
}
@Override
- public ConnectorPropertyValue getProperty(final ConfigurationStep configurationStep, final ConnectorPropertyGroup group, final ConnectorPropertyDescriptor connectorPropertyDescriptor) {
- return getProperty(configurationStep.getName(), group.getName(), connectorPropertyDescriptor.getName(), connectorPropertyDescriptor.getDefaultValue());
+ public ConnectorPropertyValue getProperty(final ConfigurationStep configurationStep, final ConnectorPropertyDescriptor connectorPropertyDescriptor) {
+ return getProperty(configurationStep.getName(), connectorPropertyDescriptor.getName(), connectorPropertyDescriptor.getDefaultValue());
+ }
+
+ @Override
+ public Set<String> getPropertyNames(final String configurationStepName) {
+ readLock.lock();
+ try {
+ final StepConfiguration config = propertyConfigurations.get(configurationStepName);
+ if (config == null) {
+ return Collections.emptySet();
+ }
+
+ return new HashSet<>(config.getPropertyValues().keySet());
+ } finally {
+ readLock.unlock();
+ }
}
@Override
- public StandardConnectorConfigurationContext createWithOverrides(final String stepName, final List<PropertyGroupConfiguration> propertyOverrides) {
+ public Set<String> getPropertyNames(final ConfigurationStep configurationStep) {
+ return getPropertyNames(configurationStep.getName());
+ }
+
+ @Override
+ public StepConfigurationContext scopedToStep(final String stepName) {
+ return new StandardStepConfigurationContext(stepName, this);
+ }
+
+ @Override
+ public StepConfigurationContext scopedToStep(final ConfigurationStep configurationStep) {
+ return scopedToStep(configurationStep.getName());
+ }
+
+ @Override
+ public StandardConnectorConfigurationContext createWithOverrides(final String stepName, final Map<String, String> propertyOverrides) {
final StandardConnectorConfigurationContext created = new StandardConnectorConfigurationContext(assetManager, secretsManager);
readLock.lock();
try {
- for (final Map.Entry<String, List<PropertyGroupConfiguration>> stepEntry : propertyGroupConfigurations.entrySet()) {
+ for (final Map.Entry<String, StepConfiguration> stepEntry : propertyConfigurations.entrySet()) {
final String existingStepName = stepEntry.getKey();
- final List<PropertyGroupConfiguration> existingGroupConfigs = stepEntry.getValue();
+ final StepConfiguration existingConfig = stepEntry.getValue();
- // If this is not the step to override, just copy the existing configs.
if (!existingStepName.equals(stepName)) {
- created.setProperties(existingStepName, existingGroupConfigs);
+ created.setProperties(existingStepName, new StepConfiguration(new HashMap<>(existingConfig.getPropertyValues())));
continue;
}
- final List<PropertyGroupConfiguration> createdGroupConfigs = new ArrayList<>();
-
- // Merge properties for this step.
- final Map<String, PropertyGroupConfiguration> existingGroupConfigMap = new HashMap<>();
- for (final PropertyGroupConfiguration existingGroupConfig : existingGroupConfigs) {
- existingGroupConfigMap.put(existingGroupConfig.groupName(), existingGroupConfig);
- }
-
- for (final PropertyGroupConfiguration override : propertyOverrides) {
- final Map<String, ConnectorValueReference> mergedProperties = new HashMap<>();
-
- final PropertyGroupConfiguration existing = existingGroupConfigMap.get(override.groupName());
- if (existing != null) {
- mergedProperties.putAll(existing.propertyValues());
+ final Map<String, ConnectorValueReference> mergedProperties = new HashMap<>(existingConfig.getPropertyValues());
+ for (final Map.Entry<String, String> override : propertyOverrides.entrySet()) {
+ final String propertyValue = override.getValue();
+ if (propertyValue == null) {
+ mergedProperties.remove(override.getKey());
+ continue;
}
- mergedProperties.putAll(override.propertyValues());
- createdGroupConfigs.add(new PropertyGroupConfiguration(override.groupName(), mergedProperties));
+ mergedProperties.put(override.getKey(), new StringLiteralValue(propertyValue));
}
-
- created.setProperties(stepName, createdGroupConfigs);
+ created.setProperties(stepName, new StepConfiguration(mergedProperties));
}
return created;
@@ -131,21 +143,22 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
@Override
- public ConfigurationUpdateResult setProperties(final String stepName, final List<PropertyGroupConfiguration> propertyGroupConfigurations) {
+ public ConfigurationUpdateResult setProperties(final String stepName, final StepConfiguration configuration) {
writeLock.lock();
try {
- final List<PropertyGroupConfiguration> existingGroupConfigs = this.propertyGroupConfigurations.get(stepName);
- if (Objects.equals(existingGroupConfigs, propertyGroupConfigurations)) {
+ final StepConfiguration existingConfig = propertyConfigurations.get(stepName);
+ final Map<String, ConnectorValueReference> existingProperties = existingConfig != null ? existingConfig.getPropertyValues() : new HashMap<>();
+ if (Objects.equals(existingProperties, configuration.getPropertyValues())) {
return ConfigurationUpdateResult.NO_CHANGES;
}
- final List<PropertyGroupConfiguration> resolvedConfigs = propertyGroupConfigurations.stream()
- .map(this::resolvePropertyGroupConfiguration)
- .toList();
+ final Map<String, ConnectorValueReference> mergedProperties = new HashMap<>(existingProperties);
+ mergedProperties.putAll(configuration.getPropertyValues());
- // Merge the new configurations with existing ones.
- this.propertyGroupConfigurations.put(stepName, merge(existingGroupConfigs, propertyGroupConfigurations));
- this.resolvedPropertyGroupConfigurations.compute(stepName, (key, existingResolvedConfigs) -> merge(existingResolvedConfigs, resolvedConfigs));
+ final StepConfiguration resolvedConfig = resolvePropertyValues(mergedProperties);
+
+ this.propertyConfigurations.put(stepName, new StepConfiguration(new HashMap<>(mergedProperties)));
+ this.resolvedPropertyConfigurations.put(stepName, resolvedConfig);
return ConfigurationUpdateResult.CHANGES_MADE;
} finally {
@@ -153,17 +166,13 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
}
}
- private PropertyGroupConfiguration resolvePropertyGroupConfiguration(final PropertyGroupConfiguration groupConfig) {
+ private StepConfiguration resolvePropertyValues(final Map<String, ConnectorValueReference> propertyValues) {
final Map<String, ConnectorValueReference> resolvedProperties = new HashMap<>();
-
- for (final Map.Entry<String, ConnectorValueReference> entry : groupConfig.propertyValues().entrySet()) {
- final String propertyName = entry.getKey();
- final ConnectorValueReference reference = entry.getValue();
- final ConnectorValueReference resolved = resolve(reference);
- resolvedProperties.put(propertyName, resolved);
+ for (final Map.Entry<String, ConnectorValueReference> entry : propertyValues.entrySet()) {
+ final ConnectorValueReference resolved = resolve(entry.getValue());
+ resolvedProperties.put(entry.getKey(), resolved);
}
-
- return new PropertyGroupConfiguration(groupConfig.groupName(), resolvedProperties);
+ return new StepConfiguration(resolvedProperties);
}
private ConnectorValueReference resolve(final ConnectorValueReference reference) {
@@ -213,20 +222,19 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
}
@Override
- public ConfigurationUpdateResult replaceProperties(final String stepName, final List<PropertyGroupConfiguration> propertyGroupConfigurations) {
+ public ConfigurationUpdateResult replaceProperties(final String stepName, final StepConfiguration configuration) {
writeLock.lock();
try {
- final List<PropertyGroupConfiguration> existingGroupConfigs = this.propertyGroupConfigurations.get(stepName);
- if (Objects.equals(existingGroupConfigs, propertyGroupConfigurations)) {
+ final StepConfiguration existingConfig = propertyConfigurations.get(stepName);
+ final Map<String, ConnectorValueReference> existingProperties = existingConfig != null ? existingConfig.getPropertyValues() : new HashMap<>();
+ if (Objects.equals(existingProperties, configuration.getPropertyValues())) {
return ConfigurationUpdateResult.NO_CHANGES;
}
- final List<PropertyGroupConfiguration> resolvedConfigs = propertyGroupConfigurations.stream()
- .map(this::resolvePropertyGroupConfiguration)
- .toList();
+ final StepConfiguration resolvedConfig = resolvePropertyValues(configuration.getPropertyValues());
- this.propertyGroupConfigurations.put(stepName, copyPropertyGroupConfigurations(propertyGroupConfigurations));
- this.resolvedPropertyGroupConfigurations.put(stepName, copyPropertyGroupConfigurations(resolvedConfigs));
+ this.propertyConfigurations.put(stepName, new StepConfiguration(new HashMap<>(configuration.getPropertyValues())));
+ this.resolvedPropertyConfigurations.put(stepName, resolvedConfig);
return ConfigurationUpdateResult.CHANGES_MADE;
} finally {
@@ -234,27 +242,17 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
}
}
- private List<PropertyGroupConfiguration> copyPropertyGroupConfigurations(final List<PropertyGroupConfiguration> groupConfigs) {
- final List<PropertyGroupConfiguration> copiedConfigs = new ArrayList<>();
-
- for (final PropertyGroupConfiguration groupConfig : groupConfigs) {
- final Map<String, ConnectorValueReference> copiedProperties = new HashMap<>(groupConfig.propertyValues());
- copiedConfigs.add(new PropertyGroupConfiguration(groupConfig.groupName(), copiedProperties));
- }
-
- return copiedConfigs;
- }
-
@Override
public ConnectorConfiguration toConnectorConfiguration() {
readLock.lock();
try {
- final List<ConfigurationStepConfiguration> stepConfigs = new ArrayList<>();
- for (final Map.Entry<String, List<PropertyGroupConfiguration>> entry : propertyGroupConfigurations.entrySet()) {
+ final Set<NamedStepConfiguration> stepConfigs = new HashSet<>();
+ for (final Map.Entry<String, StepConfiguration> entry : propertyConfigurations.entrySet()) {
final String stepName = entry.getKey();
- final List<PropertyGroupConfiguration> groupConfigurations = entry.getValue();
+ final StepConfiguration config = entry.getValue();
+ final StepConfiguration configCopy = new StepConfiguration(new HashMap<>(config.getPropertyValues()));
- stepConfigs.add(new ConfigurationStepConfiguration(stepName, groupConfigurations));
+ stepConfigs.add(new NamedStepConfiguration(stepName, configCopy));
}
return new ConnectorConfiguration(stepConfigs);
@@ -263,62 +261,14 @@ public class StandardConnectorConfigurationContext implements MutableConnectorCo
}
}
- private List<PropertyGroupConfiguration> merge(final List<PropertyGroupConfiguration> existingGroupConfigs, final List<PropertyGroupConfiguration> newGroupConfigs) {
- if (existingGroupConfigs == null || existingGroupConfigs.isEmpty()) {
- return new ArrayList<>(newGroupConfigs);
- }
-
- if (newGroupConfigs == null || newGroupConfigs.isEmpty()) {
- return existingGroupConfigs;
- }
-
- final Map<String, PropertyGroupConfiguration> mergedMap = new HashMap<>();
- for (final PropertyGroupConfiguration groupConfig : existingGroupConfigs) {
- mergedMap.put(groupConfig.groupName(), groupConfig);
- }
-
- for (final PropertyGroupConfiguration groupConfig : newGroupConfigs) {
- final PropertyGroupConfiguration existingConfiguration = mergedMap.get(groupConfig.groupName());
- final PropertyGroupConfiguration mergedGroupConfig = merge(existingConfiguration, groupConfig);
- mergedMap.put(groupConfig.groupName(), mergedGroupConfig);
- }
-
- return List.copyOf(mergedMap.values());
- }
-
- private PropertyGroupConfiguration merge(final PropertyGroupConfiguration existingConfiguration, final PropertyGroupConfiguration newConfiguration) {
- if (Objects.equals(existingConfiguration, newConfiguration)) {
- return existingConfiguration;
- }
- if (existingConfiguration == null) {
- return newConfiguration;
- }
- if (newConfiguration == null) {
- return existingConfiguration;
- }
-
- final Map<String, ConnectorValueReference> mergedProperties = new HashMap<>(existingConfiguration.propertyValues());
- mergedProperties.putAll(newConfiguration.propertyValues());
- return new PropertyGroupConfiguration(existingConfiguration.groupName(), mergedProperties);
- }
-
@Override
public MutableConnectorConfigurationContext clone() {
readLock.lock();
try {
final StandardConnectorConfigurationContext cloned = new StandardConnectorConfigurationContext(assetManager, secretsManager);
- for (final Map.Entry<String, List<PropertyGroupConfiguration>> entry : this.propertyGroupConfigurations.entrySet()) {
- final String stepName = entry.getKey();
- final List<PropertyGroupConfiguration> clonedGroupConfigs = copyPropertyGroupConfigurations(entry.getValue());
- cloned.propertyGroupConfigurations.put(stepName, clonedGroupConfigs);
+ for (final Map.Entry<String, StepConfiguration> entry : this.propertyConfigurations.entrySet()) {
+ cloned.setProperties(entry.getKey(), new StepConfiguration(new HashMap<>(entry.getValue().getPropertyValues())));
}
-
- for (final Map.Entry<String, List<PropertyGroupConfiguration>> entry : this.resolvedPropertyGroupConfigurations.entrySet()) {
- final String stepName = entry.getKey();
- final List<PropertyGroupConfiguration> clonedGroupConfigs = copyPropertyGroupConfigurations(entry.getValue());
- cloned.resolvedPropertyGroupConfigurations.put(stepName, clonedGroupConfigs);
- }
-
return cloned;
} finally {
readLock.unlock();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index d7457d0a59..c1772de292 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -37,7 +37,6 @@ import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.VersionedConfigurationStep;
-import org.apache.nifi.flow.VersionedConnectorPropertyGroup;
import org.apache.nifi.flow.VersionedConnectorValueReference;
import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.groups.ProcessGroup;
@@ -47,6 +46,8 @@ import org.apache.nifi.nar.NarCloseable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.io.UncheckedIOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
@@ -154,23 +155,15 @@ public class StandardConnectorNode implements ConnectorNode {
initializationContext.getAssetManager(), initializationContext.getSecretsManager());
for (final VersionedConfigurationStep configStep : flowConfiguration) {
- final List<PropertyGroupConfiguration> groupConfigurations = new ArrayList<>();
-
- for (final VersionedConnectorPropertyGroup propertyGroup : configStep.getPropertyGroups()) {
- final Map<String, ConnectorValueReference> convertedProperties = new HashMap<>();
- if (propertyGroup.getProperties() != null) {
- for (final Map.Entry<String, VersionedConnectorValueReference> entry : propertyGroup.getProperties().entrySet()) {
- final VersionedConnectorValueReference versionedRef = entry.getValue();
- final ConnectorValueReference valueReference = createValueReference(versionedRef);
- convertedProperties.put(entry.getKey(), valueReference);
- }
+ final Map<String, ConnectorValueReference> convertedProperties = new HashMap<>();
+ if (configStep.getProperties() != null) {
+ for (final Map.Entry<String, VersionedConnectorValueReference> entry : configStep.getProperties().entrySet()) {
+ final ConnectorValueReference valueReference = createValueReference(entry.getValue());
+ convertedProperties.put(entry.getKey(), valueReference);
}
-
- final PropertyGroupConfiguration groupConfiguration = new PropertyGroupConfiguration(propertyGroup.getName(), convertedProperties);
- groupConfigurations.add(groupConfiguration);
}
- configurationContext.setProperties(configStep.getName(), groupConfigurations);
+ configurationContext.setProperties(configStep.getName(), new StepConfiguration(convertedProperties));
}
return configurationContext;
@@ -202,8 +195,8 @@ public class StandardConnectorNode implements ConnectorNode {
// Update the active flow context based on the properties of the provided context, as the connector has now been updated.
final ConnectorConfiguration workingConfig = contextToInherit.getConfigurationContext().toConnectorConfiguration();
- for (final ConfigurationStepConfiguration stepConfig : workingConfig.getConfigurationStepConfigurations()) {
- activeFlowContext.getConfigurationContext().replaceProperties(stepConfig.stepName(), stepConfig.propertyGroupConfigurations());
+ for (final NamedStepConfiguration stepConfig : workingConfig.getNamedStepConfigurations()) {
+ activeFlowContext.getConfigurationContext().replaceProperties(stepConfig.stepName(), stepConfig.configuration());
}
// The update has been completed. Tear down and recreate the working flow context to ensure it is in a clean state.
@@ -247,9 +240,9 @@ public class StandardConnectorNode implements ConnectorNode {
}
@Override
- public void setConfiguration(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) throws FlowUpdateException {
+ public void setConfiguration(final String stepName, final StepConfiguration configuration) throws FlowUpdateException {
// Update properties and check if the configuration changed.
- final ConfigurationUpdateResult updateResult = workingFlowContext.getConfigurationContext().setProperties(stepName, groupConfigurations);
+ final ConfigurationUpdateResult updateResult = workingFlowContext.getConfigurationContext().setProperties(stepName, configuration);
if (updateResult == ConfigurationUpdateResult.NO_CHANGES) {
return;
}
@@ -487,26 +480,26 @@ public class StandardConnectorNode implements ConnectorNode {
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName) {
if (workingFlowContext == null) {
- throw new IllegalStateException("Cannot fetch Allowable Values for %s.%s.%s because %s is not being updated.".formatted(
- stepName, groupName, propertyName, this));
+ throw new IllegalStateException("Cannot fetch Allowable Values for %s.%s because %s is not being updated.".formatted(
+ stepName, propertyName, this));
}
try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
- return getConnector().fetchAllowableValues(stepName, groupName, propertyName, workingFlowContext);
+ return getConnector().fetchAllowableValues(stepName, propertyName, workingFlowContext);
}
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final String filter) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final String filter) {
if (workingFlowContext == null) {
- throw new IllegalStateException("Cannot fetch Allowable Values for %s.%s.%s because %s is not being updated.".formatted(
- stepName, groupName, propertyName, this));
+ throw new IllegalStateException("Cannot fetch Allowable Values for %s.%s because %s is not being updated.".formatted(
+ stepName, propertyName, this));
}
try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
- return getConnector().fetchAllowableValues(stepName, groupName, propertyName, workingFlowContext, filter);
+ return getConnector().fetchAllowableValues(stepName, propertyName, workingFlowContext, filter);
}
}
@@ -560,16 +553,16 @@ public class StandardConnectorNode implements ConnectorNode {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final StepConfiguration configurationOverrides) {
+ final Map<String, String> resolvedPropertyOverrides = resolvePropertyReferences(configurationOverrides);
final List<ConfigVerificationResult> results = new ArrayList<>();
- try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
+ try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
- final DescribedValueProvider allowableValueProvider = (step, groupName, propertyName) ->
- fetchAllowableValues(step, groupName, propertyName, workingFlowContext);
+ final DescribedValueProvider allowableValueProvider = (step, propertyName) ->
+ fetchAllowableValues(step, propertyName, workingFlowContext);
- final List<PropertyGroupConfiguration> propertyOverrides = Collections.unmodifiableList(groupConfigurations);
- final MutableConnectorConfigurationContext configContext = workingFlowContext.getConfigurationContext().createWithOverrides(stepName, propertyOverrides);
+ final MutableConnectorConfigurationContext configContext = workingFlowContext.getConfigurationContext().createWithOverrides(stepName, resolvedPropertyOverrides);
final ConnectorValidationContext validationContext = new StandardConnectorValidationContext(
configContext.toConnectorConfiguration(), allowableValueProvider, workingFlowContext.getParameterContext());
@@ -606,11 +599,45 @@ public class StandardConnectorNode implements ConnectorNode {
results.addAll(invalidConfigResults);
}
- results.addAll(getConnector().verifyConfigurationStep(stepName, propertyOverrides, workingFlowContext));
+ results.addAll(getConnector().verifyConfigurationStep(stepName, resolvedPropertyOverrides, workingFlowContext));
return results;
}
}
+ private Map<String, String> resolvePropertyReferences(final StepConfiguration configurationOverrides) {
+ final Map<String, String> resolvedProperties = new HashMap<>();
+
+ try {
+ for (final Map.Entry<String, ConnectorValueReference> entry : configurationOverrides.getPropertyValues().entrySet()) {
+ final String propertyName = entry.getKey();
+ final ConnectorValueReference valueReference = entry.getValue();
+ final String resolvedValue = resolvePropertyReference(valueReference);
+ resolvedProperties.put(propertyName, resolvedValue);
+ }
+ } catch (final IOException ioe) {
+ throw new UncheckedIOException("Failed to resolve Secret references for " + this, ioe);
+ }
+
+ return resolvedProperties;
+ }
+
+ private String resolvePropertyReference(final ConnectorValueReference valueReference) throws IOException {
+ if (valueReference == null) {
+ return null;
+ }
+
+ return switch (valueReference) {
+ case StringLiteralValue stringLiteralValue -> stringLiteralValue.getValue();
+ case AssetReference assetReference -> initializationContext.getAssetManager().getAsset(assetReference.getAssetIdentifier())
+ .map(asset -> asset.getFile().getAbsolutePath())
+ .orElse(null);
+ case SecretReference secretReference -> initializationContext.getSecretsManager()
+ .getSecret((SecretReference) valueReference)
+ .map(Secret::getValue)
+ .orElse(null);
+ };
+ }
+
private Optional<ConfigurationStep> getConfigurationStep(final String stepName) {
for (final ConfigurationStep step : getConfigurationSteps()) {
if (Objects.equals(step.getName(), stepName)) {
@@ -760,16 +787,16 @@ public class StandardConnectorNode implements ConnectorNode {
}
private ConnectorValidationContext createValidationContext(final FrameworkFlowContext context) {
- final DescribedValueProvider allowableValueProvider = (stepName, groupName, propertyName) ->
- fetchAllowableValues(stepName, groupName, propertyName, context);
+ final DescribedValueProvider allowableValueProvider = (stepName, propertyName) ->
+ fetchAllowableValues(stepName, propertyName, context);
final ConnectorConfiguration connectorConfiguration = context.getConfigurationContext().toConnectorConfiguration();
return new StandardConnectorValidationContext(connectorConfiguration, allowableValueProvider, context.getParameterContext());
}
- private List<DescribedValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext context) {
+ private List<DescribedValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext context) {
final List<AllowableValue> allowableValues;
try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
- allowableValues = getConnector().fetchAllowableValues(stepName, groupName, propertyName, activeFlowContext);
+ allowableValues = getConnector().fetchAllowableValues(stepName, propertyName, activeFlowContext);
}
if (allowableValues == null || allowableValues.isEmpty()) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index 4495adce91..615595d42f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -157,8 +157,8 @@ public class StandardConnectorRepository implements ConnectorRepository {
}
@Override
- public void configureConnector(final ConnectorNode connector, final String stepName, final List<PropertyGroupConfiguration> stepConfiguration) throws FlowUpdateException {
- connector.setConfiguration(stepName, stepConfiguration);
+ public void configureConnector(final ConnectorNode connector, final String stepName, final StepConfiguration configuration) throws FlowUpdateException {
+ connector.setConfiguration(stepName, configuration);
}
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationContext.java
index 8dfe8e86b9..61d36dfe8c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationContext.java
@@ -27,12 +27,11 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
public class StandardConnectorValidationContext implements ConnectorValidationContext {
private final DescribedValueProvider allowableValueProvider;
- private final Map<String, List<PropertyGroupConfiguration>> groupConfigurations;
+ private final Map<String, StepConfiguration> stepConfigurations;
private final ParameterLookup parameterLookup;
public StandardConnectorValidationContext(final ConnectorConfiguration connectorConfiguration, final DescribedValueProvider allowableValueProvider,
@@ -40,9 +39,9 @@ public class StandardConnectorValidationContext implements ConnectorValidationCo
this.allowableValueProvider = allowableValueProvider;
- groupConfigurations = new HashMap<>();
- for (final ConfigurationStepConfiguration stepConfiguration : connectorConfiguration.getConfigurationStepConfigurations()) {
- groupConfigurations.put(stepConfiguration.stepName(), stepConfiguration.propertyGroupConfigurations());
+ stepConfigurations = new HashMap<>();
+ for (final NamedStepConfiguration stepConfiguration : connectorConfiguration.getNamedStepConfigurations()) {
+ stepConfigurations.put(stepConfiguration.stepName(), stepConfiguration.configuration());
}
parameterLookup = new ParameterLookupBridge(parameterContextFacade);
@@ -50,31 +49,26 @@ public class StandardConnectorValidationContext implements ConnectorValidationCo
@Override
public ValidationContext createValidationContext(final String stepName, final String propertyGroupName) {
- final List<PropertyGroupConfiguration> propertyGroupConfigurations = groupConfigurations.getOrDefault(stepName, Collections.emptyList());
-
- for (final PropertyGroupConfiguration groupConfiguration : propertyGroupConfigurations) {
- if (Objects.equals(groupConfiguration.groupName(), propertyGroupName)) {
- final Map<String, String> stringValues = new HashMap<>();
- for (final Map.Entry<String, ConnectorValueReference> entry : groupConfiguration.propertyValues().entrySet()) {
- final ConnectorValueReference valueRef = entry.getValue();
-
- if (valueRef instanceof StringLiteralValue stringLiteral) {
- stringValues.put(entry.getKey(), stringLiteral.getValue());
- } else {
- stringValues.put(entry.getKey(), null);
- }
- }
-
- return new ConnectorValidationContextBridge(stringValues, parameterLookup);
+ final StepConfiguration stepConfig = stepConfigurations.get(stepName);
+ final Map<String, ConnectorValueReference> stepProperties = stepConfig != null ? stepConfig.getPropertyValues() : Collections.emptyMap();
+
+ final Map<String, String> stringValues = new HashMap<>();
+ for (final Map.Entry<String, ConnectorValueReference> entry : stepProperties.entrySet()) {
+ final ConnectorValueReference valueRef = entry.getValue();
+
+ if (valueRef instanceof StringLiteralValue stringLiteral) {
+ stringValues.put(entry.getKey(), stringLiteral.getValue());
+ } else {
+ stringValues.put(entry.getKey(), null);
}
}
- return new ConnectorValidationContextBridge(Collections.emptyMap(), parameterLookup);
+ return new ConnectorValidationContextBridge(stringValues, parameterLookup);
}
@Override
- public List<DescribedValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName) {
- return allowableValueProvider.fetchAllowableValues(stepName, groupName, propertyName);
+ public List<DescribedValue> fetchAllowableValues(final String stepName, final String propertyName) {
+ return allowableValueProvider.fetchAllowableValues(stepName, propertyName);
}
private static class ParameterLookupBridge implements ParameterLookup {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardStepConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardStepConfigurationContext.java
new file mode 100644
index 0000000000..b728033d89
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardStepConfigurationContext.java
@@ -0,0 +1,59 @@
+/*
+ * 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.components.connector;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class StandardStepConfigurationContext implements StepConfigurationContext {
+ private final String stepName;
+ private final ConnectorConfigurationContext parentContext;
+
+ public StandardStepConfigurationContext(final String stepName, final ConnectorConfigurationContext parentContext) {
+ this.stepName = stepName;
+ this.parentContext = parentContext;
+ }
+
+ @Override
+ public ConnectorPropertyValue getProperty(final String propertyName) {
+ return parentContext.getProperty(stepName, propertyName);
+ }
+
+ @Override
+ public ConnectorPropertyValue getProperty(final ConnectorPropertyDescriptor propertyDescriptor) {
+ return parentContext.getProperty(stepName, propertyDescriptor.getName());
+ }
+
+ @Override
+ public StepConfigurationContext createWithOverrides(final Map<String, String> propertyValues) {
+ final ConnectorConfigurationContext updatedContext = parentContext.createWithOverrides(stepName, propertyValues);
+ return updatedContext.scopedToStep(stepName);
+ }
+
+ @Override
+ public Map<String, ConnectorPropertyValue> getProperties() {
+ final Map<String, ConnectorPropertyValue> properties = new HashMap<>();
+ final Set<String> propertyNames = parentContext.getPropertyNames(stepName);
+ for (final String propertyName : propertyNames) {
+ properties.put(propertyName, getProperty(propertyName));
+ }
+
+ return properties;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index e43e2423e4..f5da41580e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -19,11 +19,11 @@ package org.apache.nifi.controller;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
+import org.apache.nifi.annotation.behavior.SupportsBatching;
+import org.apache.nifi.annotation.configuration.DefaultSettings;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
-import org.apache.nifi.annotation.behavior.SupportsBatching;
-import org.apache.nifi.annotation.configuration.DefaultSettings;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.ConfigurableComponent;
@@ -35,15 +35,17 @@ import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
import org.apache.nifi.components.connector.ConnectorPropertyGroup;
import org.apache.nifi.components.connector.ConnectorStateTransition;
+import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowContextFactory;
import org.apache.nifi.components.connector.FrameworkConnectorInitializationContext;
import org.apache.nifi.components.connector.FrameworkConnectorInitializationContextBuilder;
import org.apache.nifi.components.connector.FrameworkFlowContext;
import org.apache.nifi.components.connector.GhostConnector;
import org.apache.nifi.components.connector.MutableConnectorConfigurationContext;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.connector.SecretsManager;
import org.apache.nifi.components.connector.StandardConnectorNode;
+import org.apache.nifi.components.connector.StepConfiguration;
+import org.apache.nifi.components.connector.StringLiteralValue;
import org.apache.nifi.components.state.StateManager;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.components.validation.ValidationTrigger;
@@ -107,7 +109,6 @@ import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.net.URL;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -500,29 +501,29 @@ public class ExtensionBuilder {
final FrameworkConnectorInitializationContext initContext = createConnectorInitializationContext(managedProcessGroup, componentLog);
- final Authorizable connectorsAuthorizable = new Authorizable() {
- @Override
- public Authorizable getParentAuthorizable() {
- return null;
- }
-
- @Override
- public Resource getResource() {
- return ResourceFactory.getConnectorsResource();
- }
- };
-
- final ConnectorNode connectorNode = new StandardConnectorNode(
- identifier,
- flowController.getFlowManager(),
- extensionManager,
- connectorsAuthorizable,
- connectorDetails,
- componentType,
- activeConfigurationContext,
- connectorStateTransition,
- flowContextFactory
- );
+ final Authorizable connectorsAuthorizable = new Authorizable() {
+ @Override
+ public Authorizable getParentAuthorizable() {
+ return null;
+ }
+
+ @Override
+ public Resource getResource() {
+ return ResourceFactory.getConnectorsResource();
+ }
+ };
+
+ final ConnectorNode connectorNode = new StandardConnectorNode(
+ identifier,
+ flowController.getFlowManager(),
+ extensionManager,
+ connectorsAuthorizable,
+ connectorDetails,
+ componentType,
+ activeConfigurationContext,
+ connectorStateTransition,
+ flowContextFactory
+ );
initializeDefaultValues(connector, connectorNode.getActiveFlowContext());
// TODO: If an Exception is thrown in the call to #initialize, we should create a Ghosted Connector
@@ -536,22 +537,17 @@ public class ExtensionBuilder {
final List<ConfigurationStep> configSteps = connector.getConfigurationSteps(flowContext);
for (final ConfigurationStep step : configSteps) {
- final List<ConnectorPropertyGroup> propertyGroups = step.getPropertyGroups();
- final List<PropertyGroupConfiguration> propertyGroupConfigurations = new ArrayList<>();
-
- for (final ConnectorPropertyGroup propertyGroup : propertyGroups) {
- final Map<String, String> defaultValues = new HashMap<>();
+ final Map<String, ConnectorValueReference> defaultValues = new HashMap<>();
+ for (final ConnectorPropertyGroup propertyGroup : step.getPropertyGroups()) {
for (final ConnectorPropertyDescriptor descriptor : propertyGroup.getProperties()) {
final String name = descriptor.getName();
final String defaultValue = descriptor.getDefaultValue();
- defaultValues.put(name, defaultValue);
+ defaultValues.put(name, new StringLiteralValue(defaultValue));
}
-
- propertyGroupConfigurations.add(PropertyGroupConfiguration.fromStringValues(propertyGroup.getName(), defaultValues));
}
- flowContext.getConfigurationContext().setProperties(step.getName(), propertyGroupConfigurations);
+ flowContext.getConfigurationContext().setProperties(step.getName(), new StepConfiguration(defaultValues));
}
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
index 3a5fe24602..1b7c3b91eb 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
@@ -26,7 +26,7 @@ import org.apache.nifi.authorization.ManagedAuthorizer;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.cluster.protocol.DataFlow;
import org.apache.nifi.cluster.protocol.StandardDataFlow;
-import org.apache.nifi.components.connector.ConfigurationStepConfiguration;
+import org.apache.nifi.components.connector.NamedStepConfiguration;
import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConnectorValueReference;
@@ -35,7 +35,6 @@ import org.apache.nifi.components.connector.StringLiteralValue;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.FlowUpdateException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.Position;
@@ -67,7 +66,6 @@ import org.apache.nifi.flow.VersionedAsset;
import org.apache.nifi.flow.VersionedComponent;
import org.apache.nifi.flow.VersionedConfigurationStep;
import org.apache.nifi.flow.VersionedConnector;
-import org.apache.nifi.flow.VersionedConnectorPropertyGroup;
import org.apache.nifi.flow.VersionedConnectorValueReference;
import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedExternalFlow;
@@ -1070,19 +1068,19 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
final List<VersionedConfigurationStep> versionedConfigurationSteps = versionedConnector.getActiveFlowConfiguration();
if (versionedConfigurationSteps == null || versionedConfigurationSteps.isEmpty()) {
- return existingConfiguration != null && !existingConfiguration.getConfigurationStepConfigurations().isEmpty();
+ return existingConfiguration != null && !existingConfiguration.getNamedStepConfigurations().isEmpty();
}
- final Set<ConfigurationStepConfiguration> existingStepConfigurations = existingConfiguration.getConfigurationStepConfigurations();
+ final Set<NamedStepConfiguration> existingStepConfigurations = existingConfiguration.getNamedStepConfigurations();
if (existingStepConfigurations.size() != versionedConfigurationSteps.size()) {
return true;
}
- final Map<String, ConfigurationStepConfiguration> existingStepsByName = existingStepConfigurations.stream()
- .collect(Collectors.toMap(ConfigurationStepConfiguration::stepName, Function.identity()));
+ final Map<String, NamedStepConfiguration> existingStepsByName = existingStepConfigurations.stream()
+ .collect(Collectors.toMap(NamedStepConfiguration::stepName, Function.identity()));
for (final VersionedConfigurationStep versionedStep : versionedConfigurationSteps) {
- final ConfigurationStepConfiguration existingStep = existingStepsByName.get(versionedStep.getName());
+ final NamedStepConfiguration existingStep = existingStepsByName.get(versionedStep.getName());
if (existingStep == null) {
return true;
}
@@ -1095,44 +1093,15 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
return false;
}
- private boolean isConfigurationStepUpdated(final ConfigurationStepConfiguration existingStep, final VersionedConfigurationStep versionedStep) {
- final List<PropertyGroupConfiguration> existingPropertyGroups = existingStep.propertyGroupConfigurations();
- final List<VersionedConnectorPropertyGroup> versionedPropertyGroups = versionedStep.getPropertyGroups();
-
- if (versionedPropertyGroups == null || versionedPropertyGroups.isEmpty()) {
- return existingPropertyGroups != null && !existingPropertyGroups.isEmpty();
- }
-
- if (existingPropertyGroups.size() != versionedPropertyGroups.size()) {
- return true;
- }
-
- final Map<String, PropertyGroupConfiguration> existingGroupsByName = new HashMap<>();
- existingPropertyGroups.forEach(group -> existingGroupsByName.put(group.groupName(), group));
-
- for (final VersionedConnectorPropertyGroup versionedGroup : versionedPropertyGroups) {
- final PropertyGroupConfiguration existingGroup = existingGroupsByName.get(versionedGroup.getName());
- if (existingGroup == null) {
- return true;
- }
-
- if (isPropertyGroupUpdated(existingGroup, versionedGroup)) {
- return true;
- }
- }
-
- return false;
- }
-
- private boolean isPropertyGroupUpdated(final PropertyGroupConfiguration existingGroup, final VersionedConnectorPropertyGroup versionedGroup) {
- final Map<String, ConnectorValueReference> existingProperties = existingGroup.propertyValues();
- final Map<String, VersionedConnectorValueReference> versionedProperties = versionedGroup.getProperties();
+ private boolean isConfigurationStepUpdated(final NamedStepConfiguration existingStep, final VersionedConfigurationStep versionedStep) {
+ final Map<String, ConnectorValueReference> existingProperties = existingStep.configuration().getPropertyValues();
+ final Map<String, VersionedConnectorValueReference> versionedProperties = versionedStep.getProperties();
if (versionedProperties == null || versionedProperties.isEmpty()) {
return existingProperties != null && !existingProperties.isEmpty();
}
- if (existingProperties.size() != versionedProperties.size()) {
+ if (existingProperties == null || existingProperties.size() != versionedProperties.size()) {
return true;
}
@@ -1141,8 +1110,7 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
final VersionedConnectorValueReference versionedRef = versionedEntry.getValue();
final ConnectorValueReference existingRef = existingProperties.get(propertyName);
- final boolean valuesMatch = equals(versionedRef, existingRef);
- if (!valuesMatch) {
+ if (!equals(versionedRef, existingRef)) {
return true;
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
index d95d7c9026..a869970b16 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
@@ -24,6 +24,7 @@ import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
public class BlockingConnector implements Connector {
@@ -104,7 +105,7 @@ public class BlockingConnector implements Connector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext flowContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
return List.of();
}
@@ -114,12 +115,12 @@ public class BlockingConnector implements Connector {
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext workingContext, final String filter) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext, final String filter) {
return List.of();
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext workingContext) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext) {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicAllowableValuesConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicAllowableValuesConnector.java
index 5dbc44b0f6..597ec52e7f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicAllowableValuesConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicAllowableValuesConnector.java
@@ -65,7 +65,7 @@ public class DynamicAllowableValuesConnector extends AbstractConnector {
final ConnectorConfigurationContext configContext = flowContext.getConfigurationContext();
- if (configContext.getProperty(FILE_STEP, FILE_PROPERTY_GROUP, FILE_PATH) != null) {
+ if (configContext.getProperty(FILE_STEP, FILE_PATH) != null) {
final Set<ProcessorFacade> processorsFacades = flowContext.getRootGroup().getProcessors();
if (processorsFacades.isEmpty()) {
return steps;
@@ -112,13 +112,13 @@ public class DynamicAllowableValuesConnector extends AbstractConnector {
final VersionedExternalFlow externalFlow = VersionedFlowUtils.loadFlowFromResource("flows/choose-color.json");
final VersionedProcessGroup rootGroup = externalFlow.getFlowContents();
final VersionedProcessor processor = rootGroup.getProcessors().iterator().next();
- processor.setProperties(Map.of("File", workingContext.getConfigurationContext().getProperty(FILE_STEP, FILE_PROPERTY_GROUP, FILE_PATH).getValue()));
+ processor.setProperties(Map.of("File", workingContext.getConfigurationContext().getProperty(FILE_STEP, FILE_PATH).getValue()));
getInitializationContext().updateFlow(activeContext, externalFlow);
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext flowContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicFlowConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicFlowConnector.java
index 43f7692fe0..9c88d17836 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicFlowConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicFlowConnector.java
@@ -40,6 +40,7 @@ import org.apache.nifi.processor.util.StandardValidators;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
public class DynamicFlowConnector extends AbstractConnector {
@@ -141,7 +142,7 @@ public class DynamicFlowConnector extends AbstractConnector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext flowContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
return List.of();
}
@@ -166,13 +167,13 @@ public class DynamicFlowConnector extends AbstractConnector {
private void updateSourceStep(final FlowContext flowContext, final VersionedProcessGroup rootGroup) {
final ConnectorConfigurationContext configContext = flowContext.getConfigurationContext();
- final String sourceText = configContext.getProperty(SOURCE_STEP, SOURCE_GROUP, SOURCE_TEXT).getValue();
+ final String sourceText = configContext.getProperty(SOURCE_STEP, SOURCE_TEXT).getValue();
final VersionedProcessor sourceTextProcessor = VersionedFlowUtils.findProcessor(rootGroup,
p -> p.getType().equals(OverwriteFlowFile.class.getName())).orElseThrow();
sourceTextProcessor.getProperties().put(OverwriteFlowFile.CONTENT.getName(), sourceText);
- final boolean count = configContext.getProperty(SOURCE_STEP, SOURCE_GROUP, COUNT_FLOWFILES).asBoolean();
+ final boolean count = configContext.getProperty(SOURCE_STEP, COUNT_FLOWFILES).asBoolean();
if (count) {
final Bundle systemBundle = new Bundle();
systemBundle.setArtifact("system");
@@ -191,7 +192,7 @@ public class DynamicFlowConnector extends AbstractConnector {
private void updateDuplicationStep(final FlowContext flowContext, final VersionedProcessGroup rootGroup) {
final ConnectorConfigurationContext configContext = flowContext.getConfigurationContext();
- final int numCopies = configContext.getProperty(DUPLICATION_STEP, DUPLICATION_GROUP, NUM_COPIES).asInteger();
+ final int numCopies = configContext.getProperty(DUPLICATION_STEP, NUM_COPIES).asInteger();
final VersionedProcessor duplicateProcessor = VersionedFlowUtils.findProcessor(rootGroup,
p -> p.getType().equals(DuplicateFlowFile.class.getName())).orElseThrow();
duplicateProcessor.getProperties().put(DuplicateFlowFile.NUM_DUPLICATES.getName(), String.valueOf(numCopies));
@@ -218,7 +219,7 @@ public class DynamicFlowConnector extends AbstractConnector {
private void updateDestinationStep(final FlowContext flowContext, final VersionedProcessGroup rootGroup) {
final ConnectorConfigurationContext configContext = flowContext.getConfigurationContext();
- final boolean logContents = configContext.getProperty(DESTINATION_STEP, DESTINATION_GROUP, LOG_FLOWFILE_CONTENTS).asBoolean();
+ final boolean logContents = configContext.getProperty(DESTINATION_STEP, LOG_FLOWFILE_CONTENTS).asBoolean();
if (!logContents) {
return;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/ParameterConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/ParameterConnector.java
index abe7fb9233..60061fb4cc 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/ParameterConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/ParameterConnector.java
@@ -26,6 +26,7 @@ import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.processor.util.StandardValidators;
import java.util.List;
+import java.util.Map;
public class ParameterConnector extends AbstractConnector {
private volatile boolean initialized = false;
@@ -103,13 +104,13 @@ public class ParameterConnector extends AbstractConnector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext workingContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext workingContext) {
return List.of();
}
private void updateTextParameter(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
final ConnectorConfigurationContext configContext = workingContext.getConfigurationContext();
- final String textValue = configContext.getProperty(TEXT_STEP, TEXT_GROUP, TEXT_PROPERTY).getValue();
+ final String textValue = configContext.getProperty(TEXT_STEP, TEXT_PROPERTY).getValue();
// Update the "Text" parameter with the configured property value
final ParameterValue textParameter = new ParameterValue.Builder()
@@ -120,7 +121,7 @@ public class ParameterConnector extends AbstractConnector {
final ParameterValue sleepDurationParameter = new ParameterValue.Builder()
.name("Sleep Duration")
- .value(configContext.getProperty(TEXT_STEP, TEXT_GROUP, SLEEP_DURATION).getValue())
+ .value(configContext.getProperty(TEXT_STEP, SLEEP_DURATION).getValue())
.sensitive(false)
.build();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
index 7d7e6b1ebb..cc43b022be 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
@@ -25,6 +25,7 @@ import org.apache.nifi.flow.VersionedExternalFlow;
import java.time.Duration;
import java.util.List;
+import java.util.Map;
public class SleepingConnector implements Connector {
private final Duration sleepDuration;
@@ -98,7 +99,7 @@ public class SleepingConnector implements Connector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext activeContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext activeContext) {
return List.of();
}
@@ -108,12 +109,12 @@ public class SleepingConnector implements Connector {
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext workingContext, final String filter) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext, final String filter) {
return List.of();
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext workingContext) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext) {
return List.of();
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
index 60d2f0dd90..d0cf34c6ea 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
@@ -227,8 +227,8 @@ public class StandardConnectorNodeIT {
private void configure(final ConnectorNode connectorNode, final ConnectorConfiguration configuration) throws FlowUpdateException {
connectorNode.prepareForUpdate();
- for (final ConfigurationStepConfiguration stepConfig : configuration.getConfigurationStepConfigurations()) {
- connectorNode.setConfiguration(stepConfig.stepName(), stepConfig.propertyGroupConfigurations());
+ for (final NamedStepConfiguration stepConfig : configuration.getNamedStepConfigurations()) {
+ connectorNode.setConfiguration(stepConfig.stepName(), stepConfig.configuration());
}
connectorNode.applyUpdate();
}
@@ -295,10 +295,9 @@ public class StandardConnectorNodeIT {
assertEquals("Hello", textParameter.getValue());
// Set the value of the 'Text' property to Hi. This should result in the parameter context being updated.
- final Map<String, ConnectorValueReference> sourceProperties = Map.of("Text", new StringLiteralValue("Hi."));
- final PropertyGroupConfiguration sourcePropertyGroupConfiguration = new PropertyGroupConfiguration("Text Settings", sourceProperties);
- final ConfigurationStepConfiguration sourceConfigurationStepConfiguration = new ConfigurationStepConfiguration("Text Configuration", List.of(sourcePropertyGroupConfiguration));
- final ConnectorConfiguration connectorConfiguration = new ConnectorConfiguration(List.of(sourceConfigurationStepConfiguration));
+ final StepConfiguration sourceStepConfig = new StepConfiguration(Map.of("Text", new StringLiteralValue("Hi.")));
+ final NamedStepConfiguration sourceConfigurationStepConfiguration = new NamedStepConfiguration("Text Configuration", sourceStepConfig);
+ final ConnectorConfiguration connectorConfiguration = new ConnectorConfiguration(Set.of(sourceConfigurationStepConfiguration));
configure(connectorNode, connectorConfiguration);
assertEquals("Hi.", parameterContext.getParameter("Text").orElseThrow().getValue());
@@ -428,10 +427,9 @@ public class StandardConnectorNodeIT {
assertEquals(ValidationStatus.VALID, initialValidationState.getStatus());
assertEquals(List.of(), initialValidationState.getValidationErrors());
- final Map<String, ConnectorValueReference> sourceProperties = Map.of("Sleep Duration", new StringLiteralValue("Hi."));
- final PropertyGroupConfiguration sourcePropertyGroupConfiguration = new PropertyGroupConfiguration("Text Settings", sourceProperties);
- final ConfigurationStepConfiguration sourceConfigurationStepConfiguration = new ConfigurationStepConfiguration("Text Configuration", List.of(sourcePropertyGroupConfiguration));
- final ConnectorConfiguration connectorConfiguration = new ConnectorConfiguration(List.of(sourceConfigurationStepConfiguration));
+ final StepConfiguration sourceStepConfig = new StepConfiguration(Map.of("Sleep Duration", new StringLiteralValue("Hi.")));
+ final NamedStepConfiguration sourceConfigurationStepConfiguration = new NamedStepConfiguration("Text Configuration", sourceStepConfig);
+ final ConnectorConfiguration connectorConfiguration = new ConnectorConfiguration(Set.of(sourceConfigurationStepConfiguration));
configure(connectorNode, connectorConfiguration);
final ValidationState validationState = connectorNode.performValidation();
@@ -502,31 +500,27 @@ public class StandardConnectorNodeIT {
private ConnectorConfiguration createConnectorConfiguration(final String sourceText, final int numberOfCopies, final boolean logContents, final boolean countFlowFiles) {
// Source configuration step
- final Map<String, ConnectorValueReference> sourceProperties = Map.of(
+ final StepConfiguration sourceStepConfig = new StepConfiguration(Map.of(
"Source Text", new StringLiteralValue(sourceText),
- "Count FlowFiles", new StringLiteralValue(Boolean.toString(countFlowFiles)));
- final PropertyGroupConfiguration sourcePropertyGroupConfiguration = new PropertyGroupConfiguration("Source Settings", sourceProperties);
- final ConfigurationStepConfiguration sourceConfigurationStepConfiguration = new ConfigurationStepConfiguration("Source", List.of(sourcePropertyGroupConfiguration));
+ "Count FlowFiles", new StringLiteralValue(Boolean.toString(countFlowFiles))));
+ final NamedStepConfiguration sourceConfigurationStepConfiguration = new NamedStepConfiguration("Source", sourceStepConfig);
// Duplication configuration step
- final Map<String, ConnectorValueReference> duplicationProperties = Map.of("Number of Copies", new StringLiteralValue(Integer.toString(numberOfCopies)));
- final PropertyGroupConfiguration duplicationPropertyGroupConfiguration = new PropertyGroupConfiguration(null, duplicationProperties);
- final ConfigurationStepConfiguration duplicationConfigurationStepConfiguration = new ConfigurationStepConfiguration("Duplication", List.of(duplicationPropertyGroupConfiguration));
+ final StepConfiguration duplicationStepConfig = new StepConfiguration(Map.of("Number of Copies", new StringLiteralValue(Integer.toString(numberOfCopies))));
+ final NamedStepConfiguration duplicationConfigurationStepConfiguration = new NamedStepConfiguration("Duplication", duplicationStepConfig);
// Destination configuration step
- final Map<String, ConnectorValueReference> destinationProperties = Map.of("Log FlowFile Contents", new StringLiteralValue(Boolean.toString(logContents)));
- final PropertyGroupConfiguration destinationPropertyGroupConfiguration = new PropertyGroupConfiguration(null, destinationProperties);
- final ConfigurationStepConfiguration destinationConfigurationStepConfiguration = new ConfigurationStepConfiguration("Destination", List.of(destinationPropertyGroupConfiguration));
+ final StepConfiguration destinationStepConfig = new StepConfiguration(Map.of("Log FlowFile Contents", new StringLiteralValue(Boolean.toString(logContents))));
+ final NamedStepConfiguration destinationConfigurationStepConfiguration = new NamedStepConfiguration("Destination", destinationStepConfig);
- return new ConnectorConfiguration(List.of(sourceConfigurationStepConfiguration, duplicationConfigurationStepConfiguration, destinationConfigurationStepConfiguration));
+ return new ConnectorConfiguration(Set.of(sourceConfigurationStepConfiguration, duplicationConfigurationStepConfiguration, destinationConfigurationStepConfiguration));
}
private ConnectorConfiguration createFileConfiguration(final String filename) {
// File configuration step
- final Map<String, ConnectorValueReference> fileProperties = Map.of("File Path", new StringLiteralValue(filename));
- final PropertyGroupConfiguration filePropertyGroupConfiguration = new PropertyGroupConfiguration("", fileProperties);
- final ConfigurationStepConfiguration fileConfigurationStepConfiguration = new ConfigurationStepConfiguration("File", List.of(filePropertyGroupConfiguration));
+ final StepConfiguration fileStepConfig = new StepConfiguration(Map.of("File Path", new StringLiteralValue(filename)));
+ final NamedStepConfiguration fileConfigurationStepConfiguration = new NamedStepConfiguration("File", fileStepConfig);
- return new ConnectorConfiguration(List.of(fileConfigurationStepConfiguration));
+ return new ConnectorConfiguration(Set.of(fileConfigurationStepConfiguration));
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorConfigurationContext.java
index 2c00be4ad9..f3da3a8e50 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorConfigurationContext.java
@@ -22,7 +22,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -39,13 +38,13 @@ public class TestStandardConnectorConfigurationContext {
context = new StandardConnectorConfigurationContext(assetManager, secretsManager);
}
- private Map<String, ConnectorValueReference> toValueReferences(final Map<String, String> stringProperties) {
+ private StepConfiguration toStepConfiguration(final Map<String, String> stringProperties) {
final Map<String, ConnectorValueReference> valueReferences = new HashMap<>();
for (final Map.Entry<String, String> entry : stringProperties.entrySet()) {
final String value = entry.getValue();
valueReferences.put(entry.getKey(), value == null ? null : new StringLiteralValue(value));
}
- return valueReferences;
+ return new StepConfiguration(valueReferences);
}
@Test
@@ -54,46 +53,41 @@ public class TestStandardConnectorConfigurationContext {
properties.put("key1", "value1");
properties.put("key2", "value2");
- final PropertyGroupConfiguration group = new PropertyGroupConfiguration("group1", toValueReferences(properties));
- context.setProperties("step1", List.of(group));
+ context.setProperties("step1", toStepConfiguration(properties));
- assertEquals("value1", context.getProperty("step1", "group1", "key1").getValue());
- assertEquals("value2", context.getProperty("step1", "group1", "key2").getValue());
+ assertEquals("value1", context.getProperty("step1", "key1").getValue());
+ assertEquals("value2", context.getProperty("step1", "key2").getValue());
}
@Test
- public void testSetPropertiesAddsNewPropertyGroup() {
+ public void testSetPropertiesAddsNewProperties() {
final Map<String, String> initialProperties = new HashMap<>();
initialProperties.put("key1", "value1");
- final PropertyGroupConfiguration initialGroup = new PropertyGroupConfiguration("group1", toValueReferences(initialProperties));
- context.setProperties("step1", List.of(initialGroup));
+ context.setProperties("step1", toStepConfiguration(initialProperties));
final Map<String, String> newProperties = new HashMap<>();
newProperties.put("key3", "value3");
- final PropertyGroupConfiguration newGroup = new PropertyGroupConfiguration("group2", toValueReferences(newProperties));
- context.setProperties("step1", List.of(newGroup));
+ context.setProperties("step1", toStepConfiguration(newProperties));
- assertEquals("value1", context.getProperty("step1", "group1", "key1").getValue());
- assertEquals("value3", context.getProperty("step1", "group2", "key3").getValue());
+ assertEquals("value1", context.getProperty("step1", "key1").getValue());
+ assertEquals("value3", context.getProperty("step1", "key3").getValue());
}
@Test
- public void testSetPropertiesMergesExistingPropertyGroupWithNewValues() {
+ public void testSetPropertiesMergesExistingPropertiesWithNewValues() {
final Map<String, String> initialProperties = new HashMap<>();
initialProperties.put("key1", "value1");
initialProperties.put("key2", "value2");
- final PropertyGroupConfiguration initialGroup = new PropertyGroupConfiguration("group1", toValueReferences(initialProperties));
- context.setProperties("step1", List.of(initialGroup));
+ context.setProperties("step1", toStepConfiguration(initialProperties));
final Map<String, String> updatedProperties = new HashMap<>();
updatedProperties.put("key2", "updatedValue2");
updatedProperties.put("key3", "value3");
- final PropertyGroupConfiguration updatedGroup = new PropertyGroupConfiguration("group1", toValueReferences(updatedProperties));
- context.setProperties("step1", List.of(updatedGroup));
+ context.setProperties("step1", toStepConfiguration(updatedProperties));
- assertEquals("value1", context.getProperty("step1", "group1", "key1").getValue());
- assertEquals("updatedValue2", context.getProperty("step1", "group1", "key2").getValue());
- assertEquals("value3", context.getProperty("step1", "group1", "key3").getValue());
+ assertEquals("value1", context.getProperty("step1", "key1").getValue());
+ assertEquals("updatedValue2", context.getProperty("step1", "key2").getValue());
+ assertEquals("value3", context.getProperty("step1", "key3").getValue());
}
@Test
@@ -102,19 +96,17 @@ public class TestStandardConnectorConfigurationContext {
initialProperties.put("key1", "value1");
initialProperties.put("key2", "value2");
initialProperties.put("key3", "value3");
- final PropertyGroupConfiguration initialGroup = new PropertyGroupConfiguration("group1", toValueReferences(initialProperties));
- context.setProperties("step1", List.of(initialGroup));
+ context.setProperties("step1", toStepConfiguration(initialProperties));
- assertEquals("value2", context.getProperty("step1", "group1", "key2").getValue());
+ assertEquals("value2", context.getProperty("step1", "key2").getValue());
final Map<String, String> updatedProperties = new HashMap<>();
updatedProperties.put("key2", null);
- final PropertyGroupConfiguration updatedGroup = new PropertyGroupConfiguration("group1", toValueReferences(updatedProperties));
- context.setProperties("step1", List.of(updatedGroup));
+ context.setProperties("step1", toStepConfiguration(updatedProperties));
- assertEquals("value1", context.getProperty("step1", "group1", "key1").getValue());
- assertNull(context.getProperty("step1", "group1", "key2").getValue());
- assertEquals("value3", context.getProperty("step1", "group1", "key3").getValue());
+ assertEquals("value1", context.getProperty("step1", "key1").getValue());
+ assertNull(context.getProperty("step1", "key2").getValue());
+ assertEquals("value3", context.getProperty("step1", "key3").getValue());
}
@Test
@@ -123,94 +115,36 @@ public class TestStandardConnectorConfigurationContext {
initialProperties.put("key1", "value1");
initialProperties.put("key2", "value2");
initialProperties.put("key3", "value3");
- final PropertyGroupConfiguration initialGroup = new PropertyGroupConfiguration("group1", toValueReferences(initialProperties));
- context.setProperties("step1", List.of(initialGroup));
+ context.setProperties("step1", toStepConfiguration(initialProperties));
final Map<String, String> updatedProperties = new HashMap<>();
updatedProperties.put("key2", "updatedValue2");
- final PropertyGroupConfiguration updatedGroup = new PropertyGroupConfiguration("group1", toValueReferences(updatedProperties));
- context.setProperties("step1", List.of(updatedGroup));
+ context.setProperties("step1", toStepConfiguration(updatedProperties));
- assertEquals("value1", context.getProperty("step1", "group1", "key1").getValue());
- assertEquals("updatedValue2", context.getProperty("step1", "group1", "key2").getValue());
- assertEquals("value3", context.getProperty("step1", "group1", "key3").getValue());
- }
-
- @Test
- public void testSetPropertiesPreservesUnmodifiedGroups() {
- final Map<String, String> group1Properties = new HashMap<>();
- group1Properties.put("key1", "value1");
- final PropertyGroupConfiguration group1 = new PropertyGroupConfiguration("group1", toValueReferences(group1Properties));
-
- final Map<String, String> group2Properties = new HashMap<>();
- group2Properties.put("key2", "value2");
- final PropertyGroupConfiguration group2 = new PropertyGroupConfiguration("group2", toValueReferences(group2Properties));
-
- context.setProperties("step1", List.of(group1, group2));
-
- final Map<String, String> group3Properties = new HashMap<>();
- group3Properties.put("key3", "value3");
- final PropertyGroupConfiguration group3 = new PropertyGroupConfiguration("group3", toValueReferences(group3Properties));
-
- context.setProperties("step1", List.of(group3));
-
- assertEquals("value1", context.getProperty("step1", "group1", "key1").getValue());
- assertEquals("value2", context.getProperty("step1", "group2", "key2").getValue());
- assertEquals("value3", context.getProperty("step1", "group3", "key3").getValue());
- }
-
- @Test
- public void testSetPropertiesWithMultipleGroupsMergingAndAdding() {
- final Map<String, String> group1Properties = new HashMap<>();
- group1Properties.put("key1", "value1");
- group1Properties.put("key2", "value2");
- final PropertyGroupConfiguration group1 = new PropertyGroupConfiguration("group1", toValueReferences(group1Properties));
-
- final Map<String, String> group2Properties = new HashMap<>();
- group2Properties.put("key3", "value3");
- final PropertyGroupConfiguration group2 = new PropertyGroupConfiguration("group2", toValueReferences(group2Properties));
-
- context.setProperties("step1", List.of(group1, group2));
-
- final Map<String, String> updatedGroup1Properties = new HashMap<>();
- updatedGroup1Properties.put("key2", "updatedValue2");
- updatedGroup1Properties.put("key4", "value4");
- final PropertyGroupConfiguration updatedGroup1 = new PropertyGroupConfiguration("group1", toValueReferences(updatedGroup1Properties));
-
- final Map<String, String> group3Properties = new HashMap<>();
- group3Properties.put("key5", "value5");
- final PropertyGroupConfiguration group3 = new PropertyGroupConfiguration("group3", toValueReferences(group3Properties));
-
- context.setProperties("step1", List.of(updatedGroup1, group3));
-
- assertEquals("value1", context.getProperty("step1", "group1", "key1").getValue());
- assertEquals("updatedValue2", context.getProperty("step1", "group1", "key2").getValue());
- assertEquals("value3", context.getProperty("step1", "group2", "key3").getValue());
- assertEquals("value4", context.getProperty("step1", "group1", "key4").getValue());
- assertEquals("value5", context.getProperty("step1", "group3", "key5").getValue());
+ assertEquals("value1", context.getProperty("step1", "key1").getValue());
+ assertEquals("updatedValue2", context.getProperty("step1", "key2").getValue());
+ assertEquals("value3", context.getProperty("step1", "key3").getValue());
}
@Test
public void testSetPropertiesForDifferentSteps() {
final Map<String, String> step1Properties = new HashMap<>();
step1Properties.put("key1", "value1");
- final PropertyGroupConfiguration step1Group = new PropertyGroupConfiguration("group1", toValueReferences(step1Properties));
- context.setProperties("step1", List.of(step1Group));
+ context.setProperties("step1", toStepConfiguration(step1Properties));
final Map<String, String> step2Properties = new HashMap<>();
step2Properties.put("key2", "value2");
- final PropertyGroupConfiguration step2Group = new PropertyGroupConfiguration("group1", toValueReferences(step2Properties));
- context.setProperties("step2", List.of(step2Group));
+ context.setProperties("step2", toStepConfiguration(step2Properties));
- assertEquals("value1", context.getProperty("step1", "group1", "key1").getValue());
- assertNull(context.getProperty("step1", "group1", "key2").getValue());
- assertEquals("value2", context.getProperty("step2", "group1", "key2").getValue());
- assertNull(context.getProperty("step2", "group1", "key1").getValue());
+ assertEquals("value1", context.getProperty("step1", "key1").getValue());
+ assertNull(context.getProperty("step1", "key2").getValue());
+ assertEquals("value2", context.getProperty("step2", "key2").getValue());
+ assertNull(context.getProperty("step2", "key1").getValue());
}
@Test
public void testGetPropertyReturnsEmptyForNonExistentStep() {
- final ConnectorPropertyValue propertyValue = context.getProperty("nonExistentStep", "someGroup", "someProperty");
+ final ConnectorPropertyValue propertyValue = context.getProperty("nonExistentStep", "someProperty");
assertNull(propertyValue.getValue());
}
@@ -218,82 +152,61 @@ public class TestStandardConnectorConfigurationContext {
public void testGetPropertyReturnsEmptyForNonExistentProperty() {
final Map<String, String> properties = new HashMap<>();
properties.put("key1", "value1");
- final PropertyGroupConfiguration group = new PropertyGroupConfiguration("group1", toValueReferences(properties));
- context.setProperties("step1", List.of(group));
+ context.setProperties("step1", toStepConfiguration(properties));
- final ConnectorPropertyValue propertyValue = context.getProperty("step1", "group1", "nonExistentProperty");
+ final ConnectorPropertyValue propertyValue = context.getProperty("step1", "nonExistentProperty");
assertNull(propertyValue.getValue());
}
@Test
public void testComplexMergingScenario() {
- final Map<String, String> group1InitialProps = new HashMap<>();
- group1InitialProps.put("a", "1");
- group1InitialProps.put("b", "2");
- group1InitialProps.put("c", "3");
- final PropertyGroupConfiguration group1Initial = new PropertyGroupConfiguration("group1", toValueReferences(group1InitialProps));
-
- final Map<String, String> group2InitialProps = new HashMap<>();
- group2InitialProps.put("d", "4");
- group2InitialProps.put("e", "5");
- final PropertyGroupConfiguration group2Initial = new PropertyGroupConfiguration("group2", toValueReferences(group2InitialProps));
-
- final Map<String, String> group3InitialProps = new HashMap<>();
- group3InitialProps.put("f", "6");
- final PropertyGroupConfiguration group3Initial = new PropertyGroupConfiguration("group3", toValueReferences(group3InitialProps));
-
- context.setProperties("step1", List.of(group1Initial, group2Initial, group3Initial));
-
- final Map<String, String> group1UpdateProps = new HashMap<>();
- group1UpdateProps.put("b", null);
- group1UpdateProps.put("c", "30");
- group1UpdateProps.put("g", "7");
- final PropertyGroupConfiguration group1Update = new PropertyGroupConfiguration("group1", toValueReferences(group1UpdateProps));
-
- final Map<String, String> group4Props = new HashMap<>();
- group4Props.put("h", "8");
- final PropertyGroupConfiguration group4 = new PropertyGroupConfiguration("group4", toValueReferences(group4Props));
-
- context.setProperties("step1", List.of(group1Update, group4));
-
- assertEquals("1", context.getProperty("step1", "group1", "a").getValue());
- assertNull(context.getProperty("step1", "group1", "b").getValue());
- assertEquals("30", context.getProperty("step1", "group1", "c").getValue());
- assertEquals("4", context.getProperty("step1", "group2", "d").getValue());
- assertEquals("5", context.getProperty("step1", "group2", "e").getValue());
- assertEquals("6", context.getProperty("step1", "group3", "f").getValue());
- assertEquals("7", context.getProperty("step1", "group1", "g").getValue());
- assertEquals("8", context.getProperty("step1", "group4", "h").getValue());
+ final Map<String, String> initialProps = new HashMap<>();
+ initialProps.put("a", "1");
+ initialProps.put("b", "2");
+ initialProps.put("c", "3");
+ initialProps.put("d", "4");
+ initialProps.put("e", "5");
+ initialProps.put("f", "6");
+ context.setProperties("step1", toStepConfiguration(initialProps));
+
+ final Map<String, String> updateProps = new HashMap<>();
+ updateProps.put("b", null);
+ updateProps.put("c", "30");
+ updateProps.put("g", "7");
+ updateProps.put("h", "8");
+ context.setProperties("step1", toStepConfiguration(updateProps));
+
+ assertEquals("1", context.getProperty("step1", "a").getValue());
+ assertNull(context.getProperty("step1", "b").getValue());
+ assertEquals("30", context.getProperty("step1", "c").getValue());
+ assertEquals("4", context.getProperty("step1", "d").getValue());
+ assertEquals("5", context.getProperty("step1", "e").getValue());
+ assertEquals("6", context.getProperty("step1", "f").getValue());
+ assertEquals("7", context.getProperty("step1", "g").getValue());
+ assertEquals("8", context.getProperty("step1", "h").getValue());
}
@Test
public void testCloneHasCorrectResolvedValues() {
- final Map<String, String> group1Properties = new HashMap<>();
- group1Properties.put("key1", "value1");
- group1Properties.put("key2", "value2");
- final PropertyGroupConfiguration group1 = new PropertyGroupConfiguration("group1", toValueReferences(group1Properties));
-
- final Map<String, String> group2Properties = new HashMap<>();
- group2Properties.put("key3", "value3");
- final PropertyGroupConfiguration group2 = new PropertyGroupConfiguration("group2", toValueReferences(group2Properties));
-
- context.setProperties("step1", List.of(group1, group2));
+ final Map<String, String> step1Properties = new HashMap<>();
+ step1Properties.put("key1", "value1");
+ step1Properties.put("key2", "value2");
+ step1Properties.put("key3", "value3");
+ context.setProperties("step1", toStepConfiguration(step1Properties));
final Map<String, String> step2Properties = new HashMap<>();
step2Properties.put("keyA", "valueA");
step2Properties.put("keyB", "valueB");
- final PropertyGroupConfiguration step2Group = new PropertyGroupConfiguration("groupA", toValueReferences(step2Properties));
- context.setProperties("step2", List.of(step2Group));
+ context.setProperties("step2", toStepConfiguration(step2Properties));
final MutableConnectorConfigurationContext clonedContext = context.clone();
- assertEquals("value1", clonedContext.getProperty("step1", "group1", "key1").getValue());
- assertEquals("value2", clonedContext.getProperty("step1", "group1", "key2").getValue());
- assertEquals("value3", clonedContext.getProperty("step1", "group2", "key3").getValue());
- assertEquals("valueA", clonedContext.getProperty("step2", "groupA", "keyA").getValue());
- assertEquals("valueB", clonedContext.getProperty("step2", "groupA", "keyB").getValue());
- assertNull(clonedContext.getProperty("step1", "group1", "nonExistent").getValue());
- assertNull(clonedContext.getProperty("nonExistentStep", "group1", "key1").getValue());
+ assertEquals("value1", clonedContext.getProperty("step1", "key1").getValue());
+ assertEquals("value2", clonedContext.getProperty("step1", "key2").getValue());
+ assertEquals("value3", clonedContext.getProperty("step1", "key3").getValue());
+ assertEquals("valueA", clonedContext.getProperty("step2", "keyA").getValue());
+ assertEquals("valueB", clonedContext.getProperty("step2", "keyB").getValue());
+ assertNull(clonedContext.getProperty("step1", "nonExistent").getValue());
+ assertNull(clonedContext.getProperty("nonExistentStep", "key1").getValue());
}
}
-
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index fb37e04e29..40f2323b9b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -346,7 +346,7 @@ public class TestStandardConnectorNode {
final ConnectorConfiguration newConfiguration = createTestConfiguration();
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("testGroup", createGroupConfig());
+ connectorNode.setConfiguration("testGroup", createStepConfiguration());
connectorNode.applyUpdate();
assertEquals(newConfiguration, connectorNode.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration());
@@ -362,7 +362,7 @@ public class TestStandardConnectorNode {
final ConnectorConfiguration newConfiguration = createTestConfiguration();
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("testGroup", createGroupConfig());
+ connectorNode.setConfiguration("testGroup", createStepConfiguration());
connectorNode.applyUpdate();
assertEquals(newConfiguration, connectorNode.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration());
@@ -374,14 +374,14 @@ public class TestStandardConnectorNode {
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("step1", createGroupConfig("propertyGroup1", Map.of("prop1", "value1")));
+ connectorNode.setConfiguration("step1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.applyUpdate();
final ConnectorConfiguration newConfiguration = createTestConfiguration("step1", "prop1", "value2");
connectorNode.stop(scheduler).get(5, TimeUnit.SECONDS);
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("step1", createGroupConfig("propertyGroup1", Map.of("prop1", "value2")));
+ connectorNode.setConfiguration("step1", createStepConfiguration(Map.of("prop1", "value2")));
connectorNode.applyUpdate();
assertEquals(newConfiguration, connectorNode.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration());
}
@@ -392,7 +392,7 @@ public class TestStandardConnectorNode {
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("configurationStep1", createGroupConfig("propertyGroup1", Map.of("prop1", "value1")));
+ connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.applyUpdate();
final ConnectorConfiguration newConfiguration = createTestConfigurationWithMultipleGroups();
@@ -400,8 +400,8 @@ public class TestStandardConnectorNode {
// Wait for Connector to fully stop
connectorNode.stop(scheduler).get(5, TimeUnit.SECONDS);
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("configurationStep1", createGroupConfig("propertyGroup1", Map.of("prop1", "value1")));
- connectorNode.setConfiguration("configurationStep2", createGroupConfig("propertyGroup2", Map.of("prop2", "value2")));
+ connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
+ connectorNode.setConfiguration("configurationStep2", createStepConfiguration(Map.of("prop2", "value2")));
connectorNode.applyUpdate();
assertEquals(newConfiguration, connectorNode.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration());
@@ -413,20 +413,18 @@ public class TestStandardConnectorNode {
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("configurationStep1", createGroupConfig("propertyGroup1", Map.of("prop1", "value1")));
- connectorNode.setConfiguration("configurationStep2", createGroupConfig("propertyGroup2", Map.of("prop2", "value2")));
+ connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
+ connectorNode.setConfiguration("configurationStep2", createStepConfiguration(Map.of("prop2", "value2")));
connectorNode.applyUpdate();
connectorNode.stop(scheduler).get(5, TimeUnit.SECONDS);
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("configurationStep1", createGroupConfig("propertyGroup1", Map.of("prop1", "value1")));
+ connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.applyUpdate();
- final List<ConfigurationStepConfiguration> expectedSteps = List.of(
- new ConfigurationStepConfiguration("configurationStep1", List.of(new PropertyGroupConfiguration("propertyGroup1",
- Map.of("prop1", new StringLiteralValue("value1"))))),
- new ConfigurationStepConfiguration("configurationStep2", List.of(new PropertyGroupConfiguration("propertyGroup2",
- Map.of("prop2", new StringLiteralValue("value2")))))
+ final Set<NamedStepConfiguration> expectedSteps = Set.of(
+ new NamedStepConfiguration("configurationStep1", new StepConfiguration(Map.of("prop1", new StringLiteralValue("value1")))),
+ new NamedStepConfiguration("configurationStep2", new StepConfiguration(Map.of("prop2", new StringLiteralValue("value2"))))
);
final ConnectorConfiguration expectedConfiguration = new ConnectorConfiguration(expectedSteps);
assertEquals(expectedConfiguration, connectorNode.getActiveFlowContext().getConfigurationContext().toConnectorConfiguration());
@@ -439,7 +437,7 @@ public class TestStandardConnectorNode {
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("testGroup", createGroupConfig());
+ connectorNode.setConfiguration("testGroup", createStepConfiguration());
connectorNode.applyUpdate();
}
@@ -450,12 +448,12 @@ public class TestStandardConnectorNode {
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("configurationStep1", createGroupConfig("configurationStep1", Map.of("prop1", "value1")));
+ connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value1")));
connectorNode.applyUpdate();
trackingConnector.reset();
connectorNode.prepareForUpdate();
- connectorNode.setConfiguration("configurationStep1", createGroupConfig("configurationStep1", Map.of("prop1", "value2")));
+ connectorNode.setConfiguration("configurationStep1", createStepConfiguration(Map.of("prop1", "value2")));
connectorNode.applyUpdate();
assertTrue(trackingConnector.wasOnPropertyGroupConfiguredCalled("configurationStep1"));
@@ -468,8 +466,7 @@ public class TestStandardConnectorNode {
assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
connectorNode.prepareForUpdate();
- final List<PropertyGroupConfiguration> groupConfig = createGroupConfig("testGroup", Map.of("testProperty", "invalidValue"));
- final List<ConfigVerificationResult> results = connectorNode.verifyConfigurationStep("testStep", groupConfig);
+ final List<ConfigVerificationResult> results = connectorNode.verifyConfigurationStep("testStep", new StepConfiguration(Map.of("testProperty", new StringLiteralValue("invalidValue"))));
assertFalse(results.isEmpty());
final List<ConfigVerificationResult> failedResults = results.stream()
@@ -511,17 +508,16 @@ public class TestStandardConnectorNode {
return new ConnectorDetails(connector, bundleCoordinate, componentLog);
}
- private List<PropertyGroupConfiguration> createGroupConfig() {
- return createGroupConfig("propertyGroup1", Map.of("testProperty", "testValue"));
+ private StepConfiguration createStepConfiguration() {
+ return createStepConfiguration(Map.of("testProperty", "testValue"));
}
- private List<PropertyGroupConfiguration> createGroupConfig(final String groupName, final Map<String, String> properties) {
+ private StepConfiguration createStepConfiguration(final Map<String, String> properties) {
final Map<String, ConnectorValueReference> valueReferences = new java.util.HashMap<>();
for (final Map.Entry<String, String> entry : properties.entrySet()) {
valueReferences.put(entry.getKey(), new StringLiteralValue(entry.getValue()));
}
- final PropertyGroupConfiguration propertyGroupConfiguration = new PropertyGroupConfiguration(groupName, valueReferences);
- return List.of(propertyGroupConfiguration);
+ return new StepConfiguration(valueReferences);
}
private ConnectorConfiguration createTestConfiguration() {
@@ -529,22 +525,19 @@ public class TestStandardConnectorNode {
}
private ConnectorConfiguration createTestConfiguration(final String configurationStepName, final String propertyName, final String propertyValue) {
- final Map<String, ConnectorValueReference> properties = Map.of(propertyName, new StringLiteralValue(propertyValue));
- final PropertyGroupConfiguration propertyGroupConfiguration = new PropertyGroupConfiguration("propertyGroup1", properties);
- final ConfigurationStepConfiguration configurationStepConfiguration = new ConfigurationStepConfiguration(configurationStepName, List.of(propertyGroupConfiguration));
- return new ConnectorConfiguration(List.of(configurationStepConfiguration));
+ final StepConfiguration stepConfig = new StepConfiguration(Map.of(propertyName, new StringLiteralValue(propertyValue)));
+ final NamedStepConfiguration configurationStepConfiguration = new NamedStepConfiguration(configurationStepName, stepConfig);
+ return new ConnectorConfiguration(Set.of(configurationStepConfiguration));
}
private ConnectorConfiguration createTestConfigurationWithMultipleGroups() {
- final Map<String, ConnectorValueReference> firstConfigurationStepProperties = Map.of("prop1", new StringLiteralValue("value1"));
- final PropertyGroupConfiguration firstPropertyGroupConfiguration = new PropertyGroupConfiguration("propertyGroup1", firstConfigurationStepProperties);
- final ConfigurationStepConfiguration firstConfigurationStepConfiguration = new ConfigurationStepConfiguration("configurationStep1", List.of(firstPropertyGroupConfiguration));
+ final StepConfiguration firstStepConfig = new StepConfiguration(Map.of("prop1", new StringLiteralValue("value1")));
+ final NamedStepConfiguration firstConfigurationStepConfiguration = new NamedStepConfiguration("configurationStep1", firstStepConfig);
- final Map<String, ConnectorValueReference> secondConfigurationStepProperties = Map.of("prop2", new StringLiteralValue("value2"));
- final PropertyGroupConfiguration secondPropertyGroupConfiguration = new PropertyGroupConfiguration("propertyGroup2", secondConfigurationStepProperties);
- final ConfigurationStepConfiguration secondConfigurationStepConfiguration = new ConfigurationStepConfiguration("configurationStep2", List.of(secondPropertyGroupConfiguration));
+ final StepConfiguration secondStepConfig = new StepConfiguration(Map.of("prop2", new StringLiteralValue("value2")));
+ final NamedStepConfiguration secondConfigurationStepConfiguration = new NamedStepConfiguration("configurationStep2", secondStepConfig);
- return new ConnectorConfiguration(List.of(firstConfigurationStepConfiguration, secondConfigurationStepConfiguration));
+ return new ConnectorConfiguration(Set.of(firstConfigurationStepConfiguration, secondConfigurationStepConfiguration));
}
/**
@@ -577,7 +570,7 @@ public class TestStandardConnectorNode {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext flowContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
return List.of();
}
@@ -632,7 +625,7 @@ public class TestStandardConnectorNode {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext flowContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
return List.of();
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
index 0cf39390e1..b8f6c30aaf 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
@@ -26,11 +26,11 @@ import org.apache.nifi.components.connector.ConnectorConfigurationContext;
import org.apache.nifi.components.connector.ConnectorInitializationContext;
import org.apache.nifi.components.connector.ConnectorValidationContext;
import org.apache.nifi.components.connector.FlowUpdateException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
+import java.util.Map;
/**
* A simple no-op Connector implementation for testing purposes.
@@ -122,7 +122,7 @@ public class NopConnector implements Connector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext flowContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
return List.of();
}
@@ -150,12 +150,12 @@ public class NopConnector implements Connector {
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext workingContext, final String filter) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext, final String filter) {
return List.of();
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext workingContext) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext) {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
index 68b13e00f9..c824f8be67 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
@@ -26,11 +26,11 @@ import org.apache.nifi.components.connector.ConnectorConfigurationContext;
import org.apache.nifi.components.connector.ConnectorInitializationContext;
import org.apache.nifi.components.connector.ConnectorValidationContext;
import org.apache.nifi.components.connector.FlowUpdateException;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
+import java.util.Map;
@Tags({"test", "connector"})
public class DummyConnector implements Connector {
@@ -82,7 +82,7 @@ public class DummyConnector implements Connector {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext flowContext) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
return List.of();
}
@@ -103,12 +103,12 @@ public class DummyConnector implements Connector {
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext flowContext) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext flowContext) {
return List.of();
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext flowContext, final String filter) {
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext flowContext, final String filter) {
return List.of();
}
}
(nifi) 24/40: NIFI-15427: Added abiliy to drop flowfiles / drain flowfiles from a C… (#10730)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit ce1b4150a04731d81ad52018c74301e4b4af9092
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Jan 9 13:02:05 2026 -0500
NIFI-15427: Added abiliy to drop flowfiles / drain flowfiles from a C… (#10730)
* NIFI-15427: Added abiliy to drop flowfiles / drain flowfiles from a Connector; added some system tests to verify existing behavior; fixed existing issue with IT
* NIFI-15427: Fixed Exception message in case unable to purge FlowFiles due to Connector state
* NIFI-15427: Ensured thread safety of state transitions when draining/purging FlowFiles
* NIFI-15427: Addressed review feedback
---
.../nifi/controller/queue/DropFlowFileStatus.java | 4 +-
.../nifi/components/connector/ConnectorNode.java | 13 +++
.../nifi/components/connector/ConnectorState.java | 2 +
.../nifi/controller/queue/DropFlowFileRequest.java | 3 +-
.../nifi/components/connector/GhostConnector.java | 6 +
.../connector/StandardConnectorNode.java | 49 +++++++++
.../components/connector/BlockingConnector.java | 5 +
.../components/connector/SleepingConnector.java | 6 +
.../connector/StandardConnectorNodeIT.java | 76 ++++++++++++-
.../connector/TestStandardConnectorNode.java | 122 +++++++++++++++++++++
.../apache/nifi/controller/flow/NopConnector.java | 5 +
.../org.example.TestConnector/Another_Test_Step.md | 0
.../org.example.TestConnector/Test_Step.md | 0
.../src/test/resources/colors.txt | 3 +
.../java/org/apache/nifi/nar/DummyConnector.java | 6 +
.../tests/system/DataQueuingConnector.java | 98 +++++++++++++++++
.../org.apache.nifi.components.connector.Connector | 3 +-
.../apache/nifi/tests/system/NiFiClientUtil.java | 19 ++++
.../org/apache/nifi/tests/system/NiFiSystemIT.java | 1 +
.../connectors/ClusteredConnectorAssetsIT.java | 2 -
.../system/connectors/ClusteredConnectorIT.java | 92 ++++++++++++++++
.../tests/system/connectors/ConnectorCrudIT.java | 10 ++
.../org/apache/nifi/toolkit/client/FlowClient.java | 8 ++
.../nifi/toolkit/client/impl/JerseyFlowClient.java | 9 ++
24 files changed, 533 insertions(+), 9 deletions(-)
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileStatus.java b/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileStatus.java
index 3233465e3c..13d25bb2ed 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileStatus.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileStatus.java
@@ -17,7 +17,7 @@
package org.apache.nifi.controller.queue;
-import java.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
/**
* Represents the status of a Drop FlowFile Request that has been issued to
@@ -81,5 +81,5 @@ public interface DropFlowFileStatus {
/**
* @return a Future that can be used to determine when the drop operation has completed
*/
- Future<Void> getCompletionFuture();
+ CompletableFuture<Void> getCompletionFuture();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index b7206edbd3..2bd111c752 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -197,6 +197,19 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
Future<Void> stop(FlowEngine scheduler);
+ /**
+ * Allows the Connector to drain any in-flight data while not accepting any new data.
+ */
+ Future<Void> drainFlowFiles();
+
+ /**
+ * Purges all FlowFiles from the Connector, immediately dropping the data.
+ *
+ * @param requestor the user requesting the purge. This will be recorded in the associated provenance DROP events.
+ * @return a Future that will be completed when the purge operation is finished
+ */
+ Future<Void> purgeFlowFiles(String requestor);
+
/**
* Updates the configuration of one of the configuration steps. This method should only be invoked via the ConnectorRepository.
* @param configurationStepName the name of the configuration step being set
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorState.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorState.java
index 08e44e0bc8..ad30ab832b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorState.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorState.java
@@ -23,6 +23,8 @@ public enum ConnectorState {
STOPPING,
STOPPED,
DISABLED,
+ DRAINING,
+ PURGING,
PREPARING_FOR_UPDATE,
UPDATING,
UPDATE_FAILED,
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileRequest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileRequest.java
index 2aa22d978c..10f7fc1e13 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileRequest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileRequest.java
@@ -18,7 +18,6 @@
package org.apache.nifi.controller.queue;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
public class DropFlowFileRequest implements DropFlowFileStatus {
private final String identifier;
@@ -91,7 +90,7 @@ public class DropFlowFileRequest implements DropFlowFileStatus {
}
@Override
- public Future<Void> getCompletionFuture() {
+ public CompletableFuture<Void> getCompletionFuture() {
return completionFuture;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
index 4c7c5fa45e..73f9c98edc 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
@@ -26,6 +26,7 @@ import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
public class GhostConnector implements Connector {
private final String identifier;
@@ -119,6 +120,11 @@ public class GhostConnector implements Connector {
return List.of();
}
+ @Override
+ public CompletableFuture<Void> drainFlowFiles(final FlowContext flowContext) {
+ return CompletableFuture.completedFuture(null);
+ }
+
@Override
public String toString() {
return "GhostConnector[id=" + identifier + ", type=" + canonicalClassName + "]";
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 14713ecee9..a35e191c83 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -36,6 +36,7 @@ import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.FlowFileActivity;
import org.apache.nifi.connectable.FlowFileTransferCounts;
import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.controller.queue.DropFlowFileStatus;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.VersionedConfigurationStep;
@@ -64,6 +65,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
@@ -442,6 +444,53 @@ public class StandardConnectorNode implements ConnectorNode {
return stopCompleteFuture;
}
+ @Override
+ public Future<Void> drainFlowFiles() {
+ requireStopped("drain FlowFiles", ConnectorState.DRAINING);
+
+ try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connectorDetails.getConnector().getClass(), getIdentifier())) {
+ final CompletableFuture<Void> future = connectorDetails.getConnector().drainFlowFiles(activeFlowContext);
+ final CompletableFuture<Void> stateUpdateFuture = future.whenComplete((result, failureCause) -> stateTransition.setCurrentState(ConnectorState.STOPPED));
+ return stateUpdateFuture;
+ } catch (final Throwable t) {
+ stateTransition.setCurrentState(ConnectorState.STOPPED);
+ throw t;
+ }
+ }
+
+ @Override
+ public Future<Void> purgeFlowFiles(final String requestor) {
+ requireStopped("purge FlowFiles", ConnectorState.PURGING);
+
+ try {
+ final String dropRequestId = UUID.randomUUID().toString();
+ final DropFlowFileStatus status = activeFlowContext.getManagedProcessGroup().dropAllFlowFiles(dropRequestId, requestor);
+ final CompletableFuture<Void> future = status.getCompletionFuture();
+ final CompletableFuture<Void> stateUpdateFuture = future.whenComplete((result, failureCause) -> stateTransition.setCurrentState(ConnectorState.STOPPED));
+ return stateUpdateFuture;
+ } catch (final Throwable t) {
+ stateTransition.setCurrentState(ConnectorState.STOPPED);
+ throw t;
+ }
+ }
+
+ private void requireStopped(final String action, final ConnectorState newState) {
+ final ConnectorState desiredState = getDesiredState();
+ if (desiredState != ConnectorState.STOPPED) {
+ throw new IllegalStateException("Cannot " + action + " for " + this + " because its desired state is currently " + desiredState + "; it must be STOPPED.");
+ }
+
+ boolean stateUpdated = false;
+ while (!stateUpdated) {
+ final ConnectorState currentState = getCurrentState();
+ if (currentState != ConnectorState.STOPPED) {
+ throw new IllegalStateException("Cannot " + action + " for " + this + " because its current state is currently " + currentState + "; it must be STOPPED.");
+ }
+
+ stateUpdated = stateTransition.trySetCurrentState(ConnectorState.STOPPED, newState);
+ }
+ }
+
private void stopComponent(final FlowEngine scheduler, final CompletableFuture<Void> stopCompleteFuture) {
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connectorDetails.getConnector().getClass(), getIdentifier())) {
connectorDetails.getConnector().stop(activeFlowContext);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
index 2d163e5e80..da9836a102 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
@@ -25,6 +25,7 @@ import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
public class BlockingConnector implements Connector {
@@ -124,4 +125,8 @@ public class BlockingConnector implements Connector {
return List.of();
}
+ @Override
+ public CompletableFuture<Void> drainFlowFiles(final FlowContext flowContext) {
+ return CompletableFuture.completedFuture(null);
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
index 6443523e43..f2dbbe65c7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
@@ -26,6 +26,7 @@ import org.apache.nifi.flow.VersionedExternalFlow;
import java.time.Duration;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
public class SleepingConnector implements Connector {
private final Duration sleepDuration;
@@ -117,4 +118,9 @@ public class SleepingConnector implements Connector {
public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext workingContext) {
return List.of();
}
+
+ @Override
+ public CompletableFuture<Void> drainFlowFiles(final FlowContext flowContext) {
+ return CompletableFuture.completedFuture(null);
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
index abdbfd8f7f..d2f8ce2dfc 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
@@ -42,6 +42,8 @@ import org.apache.nifi.controller.ProcessorNode;
import org.apache.nifi.controller.ReloadComponent;
import org.apache.nifi.controller.flow.StandardFlowManager;
import org.apache.nifi.controller.flowanalysis.FlowAnalyzer;
+import org.apache.nifi.controller.queue.DropFlowFileRequest;
+import org.apache.nifi.controller.queue.DropFlowFileState;
import org.apache.nifi.controller.queue.FlowFileQueue;
import org.apache.nifi.controller.queue.FlowFileQueueFactory;
import org.apache.nifi.controller.queue.LoadBalanceCompression;
@@ -78,6 +80,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -88,6 +91,10 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
import static java.util.Objects.requireNonNull;
@@ -410,7 +417,8 @@ public class StandardConnectorNodeIT {
.anyMatch(result -> result.getSubject() != null && result.getSubject().contains("First Primary Color"));
assertTrue(hasColorError);
- final ConnectorConfiguration validConfig = createFileAndColorsConfiguration(".", "red");
+ final File colorsFile = new File("src/test/resources/colors.txt");
+ final ConnectorConfiguration validConfig = createFileAndColorsConfiguration(colorsFile.getAbsolutePath(), "red");
configure(connectorNode, validConfig);
final ValidationState updatedValidationState = connectorNode.performValidation();
@@ -439,6 +447,58 @@ public class StandardConnectorNodeIT {
assertEquals(1, validationState.getValidationErrors().size());
}
+ @Test
+ public void testPurgeFlowFilesEmptiesQueues() throws FlowUpdateException, ExecutionException, InterruptedException, TimeoutException {
+ final ConnectorNode connectorNode = initializeDynamicFlowConnector();
+ final ProcessGroup rootGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
+
+ final Connection connection = queueDataBySource(rootGroup, "Create FlowFile");
+ assertEquals(1, connection.getFlowFileQueue().size().getObjectCount());
+
+ final Future<Void> purgeFuture = connectorNode.purgeFlowFiles("test-user");
+ purgeFuture.get(10, TimeUnit.SECONDS);
+
+ assertTrue(connection.getFlowFileQueue().isEmpty());
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ }
+
+ @Test
+ public void testPurgeFlowFilesMultipleQueues() throws FlowUpdateException, ExecutionException, InterruptedException, TimeoutException {
+ final ConnectorNode connectorNode = initializeDynamicFlowConnector();
+ final ProcessGroup rootGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
+
+ final Connection connection1 = queueDataBySource(rootGroup, "Create FlowFile");
+ final Connection connection2 = queueDataByDestination(rootGroup, "Terminate FlowFile");
+ assertEquals(1, connection1.getFlowFileQueue().size().getObjectCount());
+ assertEquals(1, connection2.getFlowFileQueue().size().getObjectCount());
+
+ final Future<Void> purgeFuture = connectorNode.purgeFlowFiles("test-user");
+ purgeFuture.get(10, TimeUnit.SECONDS);
+
+ assertTrue(connection1.getFlowFileQueue().isEmpty());
+ assertTrue(connection2.getFlowFileQueue().isEmpty());
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ }
+
+ @Test
+ public void testPurgeFlowFilesRequiresStoppedState() throws FlowUpdateException {
+ final ConnectorNode connectorNode = initializeDynamicFlowConnector();
+ final ProcessGroup rootGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
+ queueDataBySource(rootGroup, "Create FlowFile");
+
+ connectorNode.enable();
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+
+ connectorNode.start(componentLifecycleThreadPool);
+ assertEquals(ConnectorState.RUNNING, connectorNode.getDesiredState());
+
+ final IllegalStateException exception = assertThrows(IllegalStateException.class, () -> connectorNode.purgeFlowFiles("test-user"));
+ assertTrue(exception.getMessage().contains("must be STOPPED"));
+
+ connectorNode.stop(componentLifecycleThreadPool);
+ }
+
+
private List<String> getConfigurationStepNames(final ConnectorNode connectorNode) {
return connectorNode.getConfigurationSteps().stream()
.map(ConfigurationStep::getName)
@@ -484,6 +544,20 @@ public class StandardConnectorNodeIT {
when(flowFileQueue.getLoadBalanceStrategy()).thenReturn(LoadBalanceStrategy.DO_NOT_LOAD_BALANCE);
when(flowFileQueue.getLoadBalanceCompression()).thenReturn(LoadBalanceCompression.DO_NOT_COMPRESS);
+ // Mock dropFlowFiles to clear the list and return a completed status
+ when(flowFileQueue.dropFlowFiles(anyString(), anyString())).thenAnswer(invocation -> {
+ final String requestId = invocation.getArgument(0);
+ final int originalCount = flowFileList.size();
+ flowFileList.clear();
+
+ final DropFlowFileRequest dropRequest = new DropFlowFileRequest(requestId);
+ dropRequest.setOriginalSize(new QueueSize(originalCount, originalCount));
+ dropRequest.setCurrentSize(new QueueSize(0, 0));
+ dropRequest.setDroppedSize(new QueueSize(originalCount, originalCount));
+ dropRequest.setState(DropFlowFileState.COMPLETE);
+ return dropRequest;
+ });
+
final FlowFileQueueFactory flowFileQueueFactory = (loadBalanceStrategy, partitioningAttribute, processGroup) -> flowFileQueue;
final Connection connection = new StandardConnection.Builder(processScheduler)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index a1490409da..bf26c1f7ed 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -494,6 +494,85 @@ public class TestStandardConnectorNode {
assertEquals("The property value is invalid", failedResult.getExplanation());
}
+ @Test
+ @Timeout(value = 5, unit = TimeUnit.SECONDS)
+ public void testDrainFlowFilesTransitionsStateToDraining() throws FlowUpdateException {
+ final CompletableFuture<Void> drainCompletionFuture = new CompletableFuture<>();
+ final DrainBlockingConnector drainBlockingConnector = new DrainBlockingConnector(drainCompletionFuture);
+ final StandardConnectorNode connectorNode = createConnectorNode(drainBlockingConnector);
+
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+
+ final Future<Void> drainFuture = connectorNode.drainFlowFiles();
+
+ assertEquals(ConnectorState.DRAINING, connectorNode.getCurrentState());
+ assertFalse(drainFuture.isDone());
+
+ drainCompletionFuture.complete(null);
+
+ try {
+ drainFuture.get(2, TimeUnit.SECONDS);
+ } catch (final Exception e) {
+ throw new RuntimeException("Drain future failed to complete", e);
+ }
+
+ assertTrue(drainFuture.isDone());
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ }
+
+ @Test
+ @Timeout(value = 5, unit = TimeUnit.SECONDS)
+ public void testDrainFlowFilesFutureDoesNotCompleteUntilDrainFinishes() throws FlowUpdateException, InterruptedException {
+ final CompletableFuture<Void> drainCompletionFuture = new CompletableFuture<>();
+ final DrainBlockingConnector drainBlockingConnector = new DrainBlockingConnector(drainCompletionFuture);
+ final StandardConnectorNode connectorNode = createConnectorNode(drainBlockingConnector);
+
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+
+ final Future<Void> drainFuture = connectorNode.drainFlowFiles();
+ assertEquals(ConnectorState.DRAINING, connectorNode.getCurrentState());
+
+ Thread.sleep(200);
+ assertFalse(drainFuture.isDone());
+ assertEquals(ConnectorState.DRAINING, connectorNode.getCurrentState());
+
+ drainCompletionFuture.complete(null);
+
+ try {
+ drainFuture.get(2, TimeUnit.SECONDS);
+ } catch (final Exception e) {
+ throw new RuntimeException("Drain future failed to complete", e);
+ }
+
+ assertTrue(drainFuture.isDone());
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ }
+
+ @Test
+ @Timeout(value = 5, unit = TimeUnit.SECONDS)
+ public void testDrainFlowFilesStateTransitionsBackToStoppedOnCompletion() throws FlowUpdateException {
+ final CompletableFuture<Void> drainCompletionFuture = new CompletableFuture<>();
+ final DrainBlockingConnector drainBlockingConnector = new DrainBlockingConnector(drainCompletionFuture);
+ final StandardConnectorNode connectorNode = createConnectorNode(drainBlockingConnector);
+
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ assertEquals(ConnectorState.STOPPED, connectorNode.getDesiredState());
+
+ final Future<Void> drainFuture = connectorNode.drainFlowFiles();
+ assertEquals(ConnectorState.DRAINING, connectorNode.getCurrentState());
+
+ drainCompletionFuture.complete(null);
+
+ try {
+ drainFuture.get(2, TimeUnit.SECONDS);
+ } catch (final Exception e) {
+ throw new RuntimeException("Drain future failed to complete", e);
+ }
+
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ assertEquals(ConnectorState.STOPPED, connectorNode.getDesiredState());
+ }
+
private StandardConnectorNode createConnectorNode() throws FlowUpdateException {
final SleepingConnector sleepingConnector = new SleepingConnector(Duration.ofMillis(1));
return createConnectorNode(sleepingConnector);
@@ -669,4 +748,47 @@ public class TestStandardConnectorNode {
}
}
+ /**
+ * Test connector that allows control over when drainFlowFiles completes via a CompletableFuture
+ */
+ private static class DrainBlockingConnector extends AbstractConnector {
+ private final CompletableFuture<Void> drainCompletionFuture;
+
+ public DrainBlockingConnector(final CompletableFuture<Void> drainCompletionFuture) {
+ this.drainCompletionFuture = drainCompletionFuture;
+ }
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ return null;
+ }
+
+ @Override
+ public void prepareForUpdate(final FlowContext workingContext, final FlowContext activeContext) {
+ }
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return List.of();
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingContext, final FlowContext activeContext) {
+ }
+
+ @Override
+ protected void onStepConfigured(final String stepName, final FlowContext workingContext) {
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
+ return List.of();
+ }
+
+ @Override
+ public CompletableFuture<Void> drainFlowFiles(final FlowContext flowContext) {
+ return drainCompletionFuture;
+ }
+ }
+
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
index b4b7c7d9be..4701b06d68 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
@@ -31,6 +31,7 @@ import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
/**
* A simple no-op Connector implementation for testing purposes.
@@ -159,4 +160,8 @@ public class NopConnector implements Connector {
return List.of();
}
+ @Override
+ public CompletableFuture<Void> drainFlowFiles(final FlowContext flowContext) {
+ return CompletableFuture.completedFuture(null);
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Another_Test_Step.md b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/steps/org.example.TestConnector/Another_Test_Step.md
similarity index 100%
rename from nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Another_Test_Step.md
rename to nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/steps/org.example.TestConnector/Another_Test_Step.md
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Test_Step.md b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/steps/org.example.TestConnector/Test_Step.md
similarity index 100%
rename from nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Test_Step.md
rename to nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/steps/org.example.TestConnector/Test_Step.md
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/colors.txt b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/colors.txt
new file mode 100644
index 0000000000..1fedccaeb9
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/colors.txt
@@ -0,0 +1,3 @@
+red
+green
+blue
\ No newline at end of file
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
index 61738f428a..ed2fdd6b07 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
@@ -31,6 +31,7 @@ import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
@Tags({"test", "connector"})
public class DummyConnector implements Connector {
@@ -111,4 +112,9 @@ public class DummyConnector implements Connector {
public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext flowContext, final String filter) {
return List.of();
}
+
+ @Override
+ public CompletableFuture<Void> drainFlowFiles(final FlowContext flowContext) {
+ return CompletableFuture.completedFuture(null);
+ }
}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
new file mode 100644
index 0000000000..de6701159d
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
@@ -0,0 +1,98 @@
+/*
+ * 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.connectors.tests.system;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.connector.AbstractConnector;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.flow.ConnectableComponent;
+import org.apache.nifi.flow.ConnectableComponentType;
+import org.apache.nifi.flow.ScheduledState;
+import org.apache.nifi.flow.VersionedConnection;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class DataQueuingConnector extends AbstractConnector {
+ @Override
+ protected void onStepConfigured(final String stepName, final FlowContext workingContext) {
+
+ }
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ final VersionedProcessor generate = new VersionedProcessor();
+ generate.setName("GenerateFlowFile");
+ generate.setType("org.apache.nifi.processors.tests.system.GenerateFlowFile");
+ generate.setIdentifier("gen-1");
+ generate.setGroupIdentifier("1234");
+ generate.setProperties(Map.of("File Size", "1 KB"));
+
+ final VersionedProcessor terminate = new VersionedProcessor();
+ terminate.setName("TerminateFlowFile");
+ terminate.setType("org.apache.nifi.processors.tests.system.TerminateFlowFile");
+ terminate.setIdentifier("term-1");
+ terminate.setGroupIdentifier("1234");
+ terminate.setScheduledState(ScheduledState.DISABLED);
+
+ final ConnectableComponent source = new ConnectableComponent();
+ source.setId(generate.getIdentifier());
+ source.setType(ConnectableComponentType.PROCESSOR);
+ source.setGroupId("1234");
+
+ final ConnectableComponent destination = new ConnectableComponent();
+ destination.setId(terminate.getIdentifier());
+ destination.setType(ConnectableComponentType.PROCESSOR);
+ destination.setGroupId("1234");
+
+ final VersionedConnection connection = new VersionedConnection();
+ connection.setSource(source);
+ connection.setDestination(destination);
+ connection.setGroupIdentifier("1234");
+ connection.setIdentifier("generate-to-terminate-1");
+
+ final VersionedProcessGroup rootGroup = new VersionedProcessGroup();
+ rootGroup.setName("Data Queuing Connector");
+ rootGroup.setIdentifier("1234");
+ rootGroup.setProcessors(Set.of(generate, terminate));
+ rootGroup.setConnections(Set.of(connection));
+
+ final VersionedExternalFlow flow = new VersionedExternalFlow();
+ flow.setFlowContents(rootGroup);
+ return flow;
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> propertyValueOverrides, final FlowContext flowContext) {
+ return List.of();
+ }
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return List.of();
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingFlowContext, final FlowContext activeFlowContext) {
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
index 85b4fb889a..c8e0d0ed64 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -15,5 +15,4 @@
org.apache.nifi.connectors.tests.system.NopConnector
org.apache.nifi.connectors.tests.system.AssetConnector
-
-
+org.apache.nifi.connectors.tests.system.DataQueuingConnector
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
index 7a16c754c9..51272e8b8e 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
@@ -80,6 +80,7 @@ import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
import org.apache.nifi.web.api.entity.ConnectionEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorsEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.ControllerServiceRunStatusEntity;
import org.apache.nifi.web.api.entity.ControllerServicesEntity;
@@ -397,6 +398,24 @@ public class NiFiClientUtil {
}
}
+ public void stopConnectors() throws NiFiClientException, IOException, InterruptedException {
+ final ConnectorsEntity connectorsEntity = nifiClient.getFlowClient().getConnectors();
+ for (final ConnectorEntity connector : connectorsEntity.getConnectors()) {
+ getConnectorClient().stopConnector(connector);
+ waitForConnectorStopped(connector.getId());
+ }
+ }
+
+ public void stopConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException, InterruptedException {
+ stopConnector(connectorEntity.getId());
+ }
+
+ public void stopConnector(final String connectorId) throws NiFiClientException, IOException, InterruptedException {
+ final ConnectorEntity entity = getConnectorClient().getConnector(connectorId);
+ getConnectorClient().stopConnector(entity);
+ waitForConnectorStopped(connectorId);
+ }
+
public void waitForConnectorStopped(final String connectorId) throws NiFiClientException, IOException, InterruptedException {
waitForConnectorState(connectorId, ConnectorState.STOPPED);
}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
index 74999daff1..b73814250f 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
@@ -261,6 +261,7 @@ public abstract class NiFiSystemIT implements NiFiInstanceProvider {
getClientUtil().disableControllerLevelServices();
getClientUtil().disableFlowAnalysisRules();
getClientUtil().stopTransmitting("root");
+ getClientUtil().stopConnectors();
getClientUtil().deleteAll("root");
getClientUtil().deleteControllerLevelServices();
getClientUtil().deleteReportingTasks();
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorAssetsIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorAssetsIT.java
index 9016c504ce..b2309891dd 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorAssetsIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorAssetsIT.java
@@ -138,5 +138,3 @@ public class ClusteredConnectorAssetsIT extends ConnectorAssetsIT {
connectorClient.deleteConnector(connector);
}
}
-
-
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorIT.java
index 2727203cf8..b07646dd17 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorIT.java
@@ -17,20 +17,28 @@
package org.apache.nifi.tests.system.connectors;
+import jakarta.ws.rs.NotFoundException;
import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.toolkit.client.ConnectorClient;
import org.apache.nifi.toolkit.client.NiFiClientException;
import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.NodeEntity;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.fail;
public class ClusteredConnectorIT extends ConnectorCrudIT {
private static final Logger logger = LoggerFactory.getLogger(ClusteredConnectorIT.class);
@@ -109,6 +117,90 @@ public class ClusteredConnectorIT extends ConnectorCrudIT {
logger.info("Validated active and working configuration on Node 2");
}
+ @Test
+ public void testDeleteConnectorOnConnect() throws NiFiClientException, IOException, InterruptedException {
+ // Create Connector
+ final ConnectorEntity connector = getClientUtil().createConnector("DataQueuingConnector");
+ assertNotNull(connector);
+
+ // Disconnect node 2
+ disconnectNode(2);
+
+ // Should not be able to delete connector
+ final ConnectorClient connectorClient = getNifiClient().getConnectorClient();
+ assertThrows(NiFiClientException.class, () -> connectorClient.deleteConnector(connector));
+
+ final NodeEntity node2Entity = getNodeEntity(2);
+ getNifiClient().getControllerClient().deleteNode(node2Entity.getNode().getNodeId());
+
+ // Should now be able to delete connector
+ connectorClient.deleteConnector(connector);
+
+ // Should now be able to add node 2 back
+ getNiFiInstance().getNodeInstance(2).stop();
+ getNiFiInstance().getNodeInstance(2).start(true);
+ waitForAllNodesConnected();
+
+ switchClientToNode(2);
+
+ // We should get a 404
+ try {
+ getNifiClient().getConnectorClient().getConnector(connector.getId());
+ fail("Expected NiFiClientException but it was not thrown");
+ } catch (final NiFiClientException e) {
+ assertInstanceOf(NotFoundException.class, e.getCause());
+ }
+ }
+
+
+ @Test
+ public void testDeleteConnectorOnConnectWithDataQueued() throws NiFiClientException, IOException, InterruptedException {
+ // Create Connector
+ final ConnectorEntity connector = getClientUtil().createConnector("DataQueuingConnector");
+ assertNotNull(connector);
+
+ getNifiClient().getConnectorClient().startConnector(connector);
+
+ Thread.sleep(1000L); // Wait 1 second to allow some data to queue
+
+ // Disconnect node 2
+ disconnectNode(2);
+ getNiFiInstance().getNodeInstance(2).stop();
+
+ // Should not be able to delete connector
+ final ConnectorClient connectorClient = getNifiClient().getConnectorClient();
+ assertThrows(NiFiClientException.class, () -> connectorClient.deleteConnector(connector));
+
+ // Remove node 2 from cluster.
+ final NodeEntity node2Entity = getNodeEntity(2);
+ getNifiClient().getControllerClient().deleteNode(node2Entity.getNode().getNodeId());
+
+ // We cannot delete the connector directly because it has data queued. Stop Node 1, delete the flow.json.gz file, and restart Node 1.
+ getNiFiInstance().getNodeInstance(1).stop();
+ final File node1InstanceDir = getNiFiInstance().getNodeInstance(1).getInstanceDirectory();
+ final File node1ConfDir = new File(node1InstanceDir, "conf");
+ final File flowJson = new File(node1ConfDir, "flow.json.gz");
+ Files.delete(flowJson.toPath());
+
+ getNiFiInstance().getNodeInstance(1).start(true);
+ waitForCoordinatorElected();
+
+ // Should now be able to add node 2 back
+ getNiFiInstance().getNodeInstance(2).start(true);
+ waitForAllNodesConnected();
+
+ switchClientToNode(2);
+
+ // We should get a 404
+ try {
+ getNifiClient().getConnectorClient().getConnector(connector.getId());
+ fail("Expected NiFiClientException but it was not thrown");
+ } catch (final NiFiClientException e) {
+ assertInstanceOf(NotFoundException.class, e.getCause());
+ }
+ }
+
+
private void assertActiveConfigurationValue(final ConnectorEntity connector, final String propertyName, final String expectedValue) {
final String actualValue = getConfigurationValue(connector.getComponent().getActiveConfiguration(), propertyName);
assertEquals(expectedValue, actualValue, "Active configuration property '" + propertyName + "' did not match expected value");
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
index 085177dad4..78728935cc 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
@@ -148,4 +148,14 @@ public class ConnectorCrudIT extends NiFiSystemIT {
getClientUtil().applyConnectorUpdate(connector);
getClientUtil().waitForValidConnector(connector.getId());
}
+
+ @Test
+ public void testDeleteConnectorNoDataQueued() throws NiFiClientException, IOException {
+ // Create Connector
+ final ConnectorEntity connector = getClientUtil().createConnector("DataQueuingConnector");
+ assertNotNull(connector);
+
+ // Delete
+ getNifiClient().getConnectorClient().deleteConnector(connector);
+ }
}
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/FlowClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/FlowClient.java
index e483f1c91a..57fa3c74f5 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/FlowClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/FlowClient.java
@@ -20,6 +20,7 @@ import org.apache.nifi.flow.VersionedReportingTaskSnapshot;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.ClusterSummaryEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
+import org.apache.nifi.web.api.entity.ConnectorsEntity;
import org.apache.nifi.web.api.entity.ControllerServiceTypesEntity;
import org.apache.nifi.web.api.entity.ControllerServicesEntity;
import org.apache.nifi.web.api.entity.CurrentUserEntity;
@@ -229,4 +230,11 @@ public interface FlowClient {
* @return list of flows
*/
VersionedFlowsEntity getFlowRegistryFlows(String registryClientId, String branch, String bucket) throws NiFiClientException, IOException;
+
+ /**
+ * Retrieves all of the Connectors.
+ *
+ * @return the list of Connectors
+ */
+ ConnectorsEntity getConnectors() throws NiFiClientException, IOException;
}
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyFlowClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyFlowClient.java
index 114c4e01c2..f0cb9450ca 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyFlowClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyFlowClient.java
@@ -32,6 +32,7 @@ import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.ClusterSummaryEntity;
import org.apache.nifi.web.api.entity.ComponentEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
+import org.apache.nifi.web.api.entity.ConnectorsEntity;
import org.apache.nifi.web.api.entity.ControllerServiceTypesEntity;
import org.apache.nifi.web.api.entity.ControllerServicesEntity;
import org.apache.nifi.web.api.entity.CurrentUserEntity;
@@ -400,4 +401,12 @@ public class JerseyFlowClient extends AbstractJerseyClient implements FlowClient
return getRequestBuilder(target).get(VersionedFlowsEntity.class);
});
}
+
+ @Override
+ public ConnectorsEntity getConnectors() throws NiFiClientException, IOException {
+ return executeAction("Error retrieving Connectors", () -> {
+ final WebTarget target = flowTarget.path("/connectors");
+ return getRequestBuilder(target).get(ConnectorsEntity.class);
+ });
+ }
}
(nifi) 11/40: NIFI-15336: Created AuthorizableSecret and implemented ParameterProviderSecretProvider to provide the Parameter Provider as the associated Authorizable (#10644)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit f4c577540e60d31076eab3ed629c430e3de6ea99
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Mon Dec 15 12:13:19 2025 -0500
NIFI-15336: Created AuthorizableSecret and implemented ParameterProviderSecretProvider to provide the Parameter Provider as the associated Authorizable (#10644)
---
.../secrets/ConnectorTestRunnerAuthorizable.java | 34 ++++++++++++++
.../secrets/ConnectorTestRunnerSecretProvider.java | 4 ++
.../connector/secrets/AuthorizableSecret.java | 25 ++++++++++
.../secrets/ParameterProviderSecretProvider.java | 1 +
.../connector/secrets/StandardSecret.java | 54 +++++++++++++++++-----
5 files changed, 106 insertions(+), 12 deletions(-)
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerAuthorizable.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerAuthorizable.java
new file mode 100644
index 0000000000..5600c34f14
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerAuthorizable.java
@@ -0,0 +1,34 @@
+/*
+ * 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.mock.connector.server.secrets;
+
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.ResourceFactory;
+
+public class ConnectorTestRunnerAuthorizable implements Authorizable {
+ @Override
+ public Authorizable getParentAuthorizable() {
+ return null;
+ }
+
+ @Override
+ public Resource getResource() {
+ return ResourceFactory.getControllerResource();
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java
index 16e05ff4f0..3323eb9eb9 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java
@@ -17,6 +17,7 @@
package org.apache.nifi.mock.connector.server.secrets;
+import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.components.connector.Secret;
import org.apache.nifi.components.connector.secrets.SecretProvider;
import org.apache.nifi.components.connector.secrets.StandardSecret;
@@ -29,6 +30,7 @@ import java.util.Map;
public class ConnectorTestRunnerSecretProvider implements SecretProvider {
public static final String GROUP_NAME = "Default";
+ private static final Authorizable AUTHORIZABLE = new ConnectorTestRunnerAuthorizable();
private final Map<String, String> secrets = new HashMap<>();
@@ -55,6 +57,7 @@ public class ConnectorTestRunnerSecretProvider implements SecretProvider {
.groupName(GROUP_NAME)
.name(entry.getKey())
.value(entry.getValue())
+ .authorizable(AUTHORIZABLE)
.build();
secrets.add(secret);
@@ -76,6 +79,7 @@ public class ConnectorTestRunnerSecretProvider implements SecretProvider {
.groupName(GROUP_NAME)
.name(secretName)
.value(value)
+ .authorizable(AUTHORIZABLE)
.build();
matchingSecrets.add(secret);
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/components/connector/secrets/AuthorizableSecret.java b/nifi-framework-api/src/main/java/org/apache/nifi/components/connector/secrets/AuthorizableSecret.java
new file mode 100644
index 0000000000..3bcf6b099c
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/components/connector/secrets/AuthorizableSecret.java
@@ -0,0 +1,25 @@
+/*
+ * 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.components.connector.secrets;
+
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.components.connector.Secret;
+
+public interface AuthorizableSecret extends Secret, Authorizable {
+
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java
index 2f058f3c2a..b7d37efc19 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java
@@ -67,6 +67,7 @@ public class ParameterProviderSecretProvider implements SecretProvider {
.name(descriptor.getName())
.description(descriptor.getDescription())
.value(parameter.getValue())
+ .authorizable(parameterProvider)
.build();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java
index 443b19cd06..f2da84d233 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java
@@ -17,16 +17,18 @@
package org.apache.nifi.components.connector.secrets;
-import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.resource.Authorizable;
import java.util.Objects;
-public class StandardSecret implements Secret {
+public class StandardSecret implements AuthorizableSecret {
private final String providerName;
private final String groupName;
private final String name;
private final String description;
private final String value;
+ private final Authorizable authorizable;
private StandardSecret(final Builder builder) {
this.providerName = builder.providerName;
@@ -34,6 +36,7 @@ public class StandardSecret implements Secret {
this.name = builder.name;
this.description = builder.description;
this.value = builder.value;
+ this.authorizable = builder.authorizable;
}
@Override
@@ -63,13 +66,12 @@ public class StandardSecret implements Secret {
@Override
public String toString() {
- return "StandardSecret[providerName=%s, groupName=%s, name=%s, description=%s]".formatted(
- providerName, groupName, name, description);
+ return "StandardSecret[providerName=%s, groupName=%s, name=%s]".formatted(providerName, groupName, name);
}
@Override
public int hashCode() {
- return Objects.hash(providerName, groupName, name, description);
+ return Objects.hash(providerName, groupName, name);
}
@Override
@@ -83,8 +85,17 @@ public class StandardSecret implements Secret {
final StandardSecret other = (StandardSecret) obj;
return Objects.equals(this.providerName, other.providerName)
&& Objects.equals(this.groupName, other.groupName)
- && Objects.equals(this.name, other.name)
- && Objects.equals(this.description, other.description);
+ && Objects.equals(this.name, other.name);
+ }
+
+ @Override
+ public Authorizable getParentAuthorizable() {
+ return authorizable.getParentAuthorizable();
+ }
+
+ @Override
+ public Resource getResource() {
+ return authorizable.getResource();
}
public static class Builder {
@@ -93,33 +104,52 @@ public class StandardSecret implements Secret {
private String name;
private String description;
private String value;
+ private Authorizable authorizable;
- public Builder providerName(String providerName) {
+ public Builder providerName(final String providerName) {
this.providerName = providerName;
return this;
}
- public Builder groupName(String groupName) {
+ public Builder groupName(final String groupName) {
this.groupName = groupName;
return this;
}
- public Builder name(String name) {
+ public Builder name(final String name) {
this.name = name;
return this;
}
- public Builder description(String description) {
+ public Builder description(final String description) {
this.description = description;
return this;
}
- public Builder value(String value) {
+ public Builder value(final String value) {
this.value = value;
return this;
}
+ public Builder authorizable(final Authorizable authorizable) {
+ this.authorizable = authorizable;
+ return this;
+ }
+
public StandardSecret build() {
+ if (providerName == null) {
+ throw new IllegalStateException("Provider name is required");
+ }
+ if (groupName == null) {
+ throw new IllegalStateException("Group name is required");
+ }
+ if (name == null) {
+ throw new IllegalStateException("Secret name is required");
+ }
+ if (authorizable == null) {
+ throw new IllegalStateException("Authorizable is required");
+ }
+
return new StandardSecret(this);
}
}
(nifi) 02/40: NIFI-15258: Fixed vulnerable lz4 dependency
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit e04d6729dccd627a2459dbd0f6f864e885d52297
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Mon Dec 8 20:11:11 2025 -0500
NIFI-15258: Fixed vulnerable lz4 dependency
---
.../nifi-kafka-to-s3-integration-tests/pom.xml | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml
index c36971572c..345f9b1a38 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml
@@ -65,11 +65,23 @@
</dependency>
<!-- Kafka client for producing test data -->
+ <!-- Override vulnerable kafka clients -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
- <version>3.9.0</version>
- <scope>test</scope>
+ <version>4.1.1</version>
+ <exclusions>
+ <!-- Excluded and replaced with at.yawk.lz4 version -->
+ <exclusion>
+ <groupId>org.lz4</groupId>
+ <artifactId>lz4-java</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>at.yawk.lz4</groupId>
+ <artifactId>lz4-java</artifactId>
+ <version>1.10.1</version>
</dependency>
<!-- Confluent Schema Registry and Avro dependencies for testing -->
(nifi) 16/40: NIFI-15361: Allowing configuration step documentation to be returned … (#10667)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit e1f0916fe7f23d47036f013a2ead0ace43641e87
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Thu Dec 18 16:51:09 2025 -0500
NIFI-15361: Allowing configuration step documentation to be returned … (#10667)
* NIFI-15361: Allowing configuration step documentation to be returned independent of any connector instances.
---
.../connectors/kafkas3/KafkaConnectionStep.java | 32 -----------
.../org.apache.nifi.components.connector.Connector | 1 +
.../Kafka_Connection.md | 27 +++++++++
.../Kafka_Topics.md | 34 +++++++++++
.../S3_Configuration.md | 65 ++++++++++++++++++++++
.../api/dto/ConfigurationStepConfigurationDTO.java | 14 ++---
.../web/api/entity/StepDocumentationEntity.java | 35 ++++++++++++
.../nifi/manifest/RuntimeManifestService.java | 16 +++++-
.../manifest/StandardRuntimeManifestService.java | 45 +++++++++++++++
.../StandardRuntimeManifestServiceTest.java | 48 ++++++++++++++++
.../org.example.TestConnector/Another_Test_Step.md | 4 ++
.../org.example.TestConnector/Test_Step.md | 8 +++
.../org/apache/nifi/web/NiFiServiceFacade.java | 12 ++++
.../apache/nifi/web/StandardNiFiServiceFacade.java | 5 ++
.../java/org/apache/nifi/web/api/FlowResource.java | 45 +++++++++++++++
.../org/apache/nifi/web/api/dto/DtoFactory.java | 34 ++++++++++-
.../configuration/WebApplicationConfiguration.java | 1 +
.../nifi/web/controller/ControllerFacade.java | 16 ++++++
.../web/dao/impl/StandardConnectorDAOTest.java | 23 +++++++-
19 files changed, 420 insertions(+), 45 deletions(-)
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java
index 22ae253a98..793b1e9be0 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java
@@ -29,37 +29,6 @@ import java.util.List;
public class KafkaConnectionStep {
public static final String STEP_NAME = "Kafka Connection";
- // Temporary documentation to verify the inline documentation feature in the UI.
- // In practice, this content should probably be loaded from an external file.
- private static final String KAFKA_CONNECTION_DOCUMENTATION = """
- # Kafka Connection Configuration
-
- This step configures the connection to your Apache Kafka cluster.
-
- ## Kafka Server Settings
-
- Enter the bootstrap servers for your Kafka cluster. You can specify multiple brokers
- as a comma-separated list (e.g., `broker1:9092,broker2:9092,broker3:9092`).
-
- ### Security Configuration
-
- Select the appropriate security protocol based on your Kafka cluster configuration:
-
- | Protocol | Description |
- |----------|-------------|
- | PLAINTEXT | No encryption or authentication |
- | SSL | TLS encryption without SASL authentication |
- | SASL_PLAINTEXT | SASL authentication without encryption |
- | SASL_SSL | Both SASL authentication and TLS encryption (recommended) |
-
- If using SASL authentication, provide your username and password credentials.
-
- ## Schema Registry (Optional)
-
- If your Kafka topics use Avro, Protobuf, or JSON Schema, configure the Schema Registry
- URL to enable schema-based serialization and deserialization.
- """;
-
public static final ConnectorPropertyDescriptor KAFKA_BROKERS = new ConnectorPropertyDescriptor.Builder()
.name("Kafka Brokers")
.description("A comma-separated list of Kafka brokers to connect to.")
@@ -158,7 +127,6 @@ public class KafkaConnectionStep {
public static final ConfigurationStep KAFKA_CONNECTION_STEP = new ConfigurationStep.Builder()
.name(STEP_NAME)
.description("Configure Kafka connection settings")
- .documentation(KAFKA_CONNECTION_DOCUMENTATION)
.propertyGroups(List.of(
KAFKA_SERVER_GROUP,
SCHEMA_REGISTRY_GROUP
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
index de5fe10a48..d5b0fe6db6 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -14,3 +14,4 @@
# limitations under the License.
org.apache.nifi.connectors.kafkas3.KafkaToS3
+
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Connection.md b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Connection.md
new file mode 100644
index 0000000000..1621b37061
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Connection.md
@@ -0,0 +1,27 @@
+# Kafka Connection Configuration
+
+This step configures the connection to your Apache Kafka cluster.
+
+## Kafka Server Settings
+
+Enter the bootstrap servers for your Kafka cluster. You can specify multiple brokers
+as a comma-separated list (e.g., `broker1:9092,broker2:9092,broker3:9092`).
+
+### Security Configuration
+
+Select the appropriate security protocol based on your Kafka cluster configuration:
+
+| Protocol | Description |
+|----------|-------------|
+| PLAINTEXT | No encryption or authentication |
+| SSL | TLS encryption without SASL authentication |
+| SASL_PLAINTEXT | SASL authentication without encryption |
+| SASL_SSL | Both SASL authentication and TLS encryption (recommended) |
+
+If using SASL authentication, provide your username and password credentials.
+
+## Schema Registry (Optional)
+
+If your Kafka topics use Avro, Protobuf, or JSON Schema, configure the Schema Registry
+URL to enable schema-based serialization and deserialization.
+
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Topics.md b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Topics.md
new file mode 100644
index 0000000000..2e2739676c
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Topics.md
@@ -0,0 +1,34 @@
+# Kafka Topics Configuration
+
+This step configures which Kafka topics to consume from and how to consume them.
+
+## Topic Names
+
+Select one or more Kafka topics to consume from. The connector will automatically
+fetch the list of available topics from your Kafka cluster.
+
+## Consumer Group ID
+
+Specify a unique consumer group ID for this connector. Kafka uses consumer groups
+to track message offsets and ensure each message is processed only once within a group.
+
+**Best Practice:** Use a descriptive name that identifies the purpose of this connector,
+such as `kafka-to-s3-production` or `analytics-pipeline-consumer`.
+
+## Offset Reset
+
+Controls the behavior when no prior offset exists or the current offset is invalid:
+
+| Value | Description |
+|-------|-------------|
+| earliest | Start reading from the oldest available message |
+| latest | Start reading from the newest messages only |
+| none | Fail if no prior offset exists |
+
+## Kafka Data Format
+
+Specify the format of messages in your Kafka topics:
+
+- **Avro**: Messages are encoded using Apache Avro (requires Schema Registry)
+- **JSON**: Messages are plain JSON objects
+
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/S3_Configuration.md b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/S3_Configuration.md
new file mode 100644
index 0000000000..fcd6092d43
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/S3_Configuration.md
@@ -0,0 +1,65 @@
+# S3 Configuration
+
+This step configures the connection to Amazon S3 or an S3-compatible storage system.
+
+## S3 Destination Configuration
+
+### S3 Bucket
+
+The name of the S3 bucket where data will be written. Ensure the bucket exists
+and your credentials have write permissions.
+
+### S3 Prefix
+
+An optional prefix (folder path) to prepend to all object keys. For example,
+setting this to `kafka-data/` will result in objects like:
+`kafka-data/2024/01/15/data-001.json`
+
+### S3 Region
+
+The AWS region where your S3 bucket is located (e.g., `us-east-1`, `eu-west-1`).
+
+### S3 Data Format
+
+The format to use when writing objects to S3:
+
+- **JSON**: Write data as JSON objects (human-readable)
+- **Avro**: Write data in Apache Avro format (compact, schema-embedded)
+
+### S3 Endpoint Override URL
+
+Leave this blank for standard AWS S3. Use this field only when connecting to
+S3-compatible storage systems like MinIO, LocalStack, or Ceph.
+
+## Merge Configuration
+
+### Target Object Size
+
+The connector merges multiple Kafka messages together before writing to S3 to
+reduce the number of objects created. Specify the target size for merged objects
+(e.g., `256 MB`, `1 GB`).
+
+### Merge Latency
+
+The maximum time to wait while collecting messages before writing to S3. Even if
+the target size hasn't been reached, data will be written after this duration to
+ensure timely data availability.
+
+## S3 Credentials
+
+### Authentication Strategy
+
+Choose how to authenticate with AWS:
+
+| Strategy | Description |
+|----------|-------------|
+| Access Key ID and Secret Key | Use explicit AWS access credentials |
+| Default AWS Credentials | Use the default credential chain (environment variables, IAM roles, etc.) |
+
+### S3 Access Key ID / Secret Access Key
+
+When using explicit credentials, provide your AWS access key ID and secret access key.
+
+**Security Note:** Store sensitive credentials using NiFi's sensitive property
+protection or parameter contexts with secret providers.
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
index 94b0c7ff71..4b58f9df2c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
@@ -29,7 +29,7 @@ public class ConfigurationStepConfigurationDTO {
private String configurationStepName;
private String configurationStepDescription;
- private String configurationStepDocumentation;
+ private boolean documented;
private List<PropertyGroupConfigurationDTO> propertyGroupConfigurations;
/**
@@ -57,15 +57,15 @@ public class ConfigurationStepConfigurationDTO {
}
/**
- * @return the configuration step documentation in markdown
+ * @return whether this step has extended documentation available
*/
- @Schema(description = "Extended documentation or help text for the configuration step.")
- public String getConfigurationStepDocumentation() {
- return configurationStepDocumentation;
+ @Schema(description = "Whether extended documentation is available for this configuration step.")
+ public boolean isDocumented() {
+ return documented;
}
- public void setConfigurationStepDocumentation(final String configurationStepDocumentation) {
- this.configurationStepDocumentation = configurationStepDocumentation;
+ public void setDocumented(final boolean documented) {
+ this.documented = documented;
}
/**
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/StepDocumentationEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/StepDocumentationEntity.java
new file mode 100644
index 0000000000..e01da34a1d
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/StepDocumentationEntity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.web.api.entity;
+
+import jakarta.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "stepDocumentationEntity")
+public class StepDocumentationEntity extends Entity {
+
+ private String stepDocumentation;
+
+ public String getStepDocumentation() {
+ return stepDocumentation;
+ }
+
+ public void setStepDocumentation(final String stepDocumentation) {
+ this.stepDocumentation = stepDocumentation;
+ }
+
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/manifest/RuntimeManifestService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/manifest/RuntimeManifestService.java
index 3acbfda5b4..60c9463cbd 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/manifest/RuntimeManifestService.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/manifest/RuntimeManifestService.java
@@ -43,12 +43,24 @@ public interface RuntimeManifestService {
RuntimeManifest getManifestForBundle(String group, String artifact, String version);
/**
- * Returns a mapping of additionalDetails for the speicfied bundle.
+ * Returns a mapping of additionalDetails for the specified bundle.
*
* @param group The bundle group
* @param artifact The bundle artifact
* @param version The bundle version
- * @return The additionaDetails mapping
+ * @return The additionalDetails mapping
*/
Map<String, File> discoverAdditionalDetails(String group, String artifact, String version);
+
+ /**
+ * Returns a mapping of step documentation files for the specified Connector type.
+ * The keys are the step names and the values are the corresponding documentation files.
+ *
+ * @param group The bundle group
+ * @param artifact The bundle artifact
+ * @param version The bundle version
+ * @param connectorType The fully qualified class name of the Connector
+ * @return The step documentation mapping
+ */
+ Map<String, File> discoverStepDocumentation(String group, String artifact, String version, String connectorType);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java
index 1a7f3ef214..197951ccb0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java
@@ -401,6 +401,51 @@ public class StandardRuntimeManifestService implements RuntimeManifestService {
return additionalDetailsMap;
}
+ @Override
+ public Map<String, File> discoverStepDocumentation(final String group, final String artifact, final String version, final String connectorType) {
+ final BundleCoordinate bundleCoordinate = new BundleCoordinate(group, artifact, version);
+ final Bundle bundle = extensionManager.getBundle(bundleCoordinate);
+
+ if (bundle == null) {
+ throw new ResourceNotFoundException("Unable to find bundle [" + bundleCoordinate + "]");
+ }
+
+ return discoverStepDocumentation(bundle.getBundleDetails(), connectorType);
+ }
+
+ private Map<String, File> discoverStepDocumentation(final BundleDetails bundleDetails, final String connectorType) {
+ final Map<String, File> stepDocsMap = new LinkedHashMap<>();
+
+ final File stepDocsDir = new File(bundleDetails.getWorkingDirectory(), "META-INF/docs/step-documentation/" + connectorType);
+ if (!stepDocsDir.exists()) {
+ LOGGER.debug("No step-documentation directory found for [{}] under [{}]", connectorType, bundleDetails.getWorkingDirectory().getAbsolutePath());
+ return stepDocsMap;
+ }
+
+ final File[] stepDocFiles = stepDocsDir.listFiles();
+ if (stepDocFiles == null) {
+ return stepDocsMap;
+ }
+
+ for (final File stepDocFile : stepDocFiles) {
+ if (!stepDocFile.isFile() || !stepDocFile.getName().endsWith(".md")) {
+ LOGGER.debug("Skipping [{}], not a markdown file...", stepDocFile.getAbsolutePath());
+ continue;
+ }
+
+ final String fileName = stepDocFile.getName().substring(0, stepDocFile.getName().length() - 3);
+ final String stepName = fileNameToStepName(fileName);
+ stepDocsMap.put(stepName, stepDocFile);
+ LOGGER.debug("Discovered step documentation for step [{}] at [{}]", stepName, stepDocFile.getAbsolutePath());
+ }
+
+ return stepDocsMap;
+ }
+
+ private String fileNameToStepName(final String fileName) {
+ return fileName.replace("_", " ");
+ }
+
private Map<String, String> loadAdditionalDetails(final BundleDetails bundleDetails) {
final Map<String, File> additionalDetailsMap = discoverAdditionalDetails(bundleDetails);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/manifest/StandardRuntimeManifestServiceTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/manifest/StandardRuntimeManifestServiceTest.java
index fae3db4f85..fe9b3d93ef 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/manifest/StandardRuntimeManifestServiceTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/manifest/StandardRuntimeManifestServiceTest.java
@@ -31,6 +31,7 @@ import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.PythonBundle;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.python.PythonProcessorDetails;
+import org.apache.nifi.web.ResourceNotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -38,11 +39,14 @@ import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import static java.util.Collections.emptySet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -163,6 +167,50 @@ public class StandardRuntimeManifestServiceTest {
assertEquals(0, controllerServiceDefinitions.size());
}
+ @Test
+ public void testDiscoverStepDocumentation() {
+ when(extensionManager.getBundle(testComponentsBundle.getBundleDetails().getCoordinate())).thenReturn(testComponentsBundle);
+
+ final BundleCoordinate coordinate = testComponentsBundle.getBundleDetails().getCoordinate();
+ final Map<String, File> stepDocs = runtimeManifestService.discoverStepDocumentation(
+ coordinate.getGroup(), coordinate.getId(), coordinate.getVersion(), "org.example.TestConnector");
+
+ assertNotNull(stepDocs);
+ assertEquals(2, stepDocs.size());
+ assertTrue(stepDocs.containsKey("Test Step"));
+ assertTrue(stepDocs.containsKey("Another Test Step"));
+
+ final File testStepFile = stepDocs.get("Test Step");
+ assertNotNull(testStepFile);
+ assertTrue(testStepFile.exists());
+ assertEquals("Test_Step.md", testStepFile.getName());
+
+ final File anotherTestStepFile = stepDocs.get("Another Test Step");
+ assertNotNull(anotherTestStepFile);
+ assertTrue(anotherTestStepFile.exists());
+ assertEquals("Another_Test_Step.md", anotherTestStepFile.getName());
+ }
+
+ @Test
+ public void testDiscoverStepDocumentationWithNonExistentConnector() {
+ when(extensionManager.getBundle(testComponentsBundle.getBundleDetails().getCoordinate())).thenReturn(testComponentsBundle);
+
+ final BundleCoordinate coordinate = testComponentsBundle.getBundleDetails().getCoordinate();
+ final Map<String, File> stepDocs = runtimeManifestService.discoverStepDocumentation(
+ coordinate.getGroup(), coordinate.getId(), coordinate.getVersion(), "org.example.NonExistentConnector");
+
+ assertNotNull(stepDocs);
+ assertTrue(stepDocs.isEmpty());
+ }
+
+ @Test
+ public void testDiscoverStepDocumentationWithNonExistentBundle() {
+ when(extensionManager.getBundle(new BundleCoordinate("org.example", "nonexistent", "1.0.0"))).thenReturn(null);
+
+ assertThrows(ResourceNotFoundException.class, () ->
+ runtimeManifestService.discoverStepDocumentation("org.example", "nonexistent", "1.0.0", "org.example.TestConnector"));
+ }
+
/**
* Override getFrameworkBundle to provide a mocked Bundle.
*/
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Another_Test_Step.md b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Another_Test_Step.md
new file mode 100644
index 0000000000..4673395a0c
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Another_Test_Step.md
@@ -0,0 +1,4 @@
+# Another Test Step
+
+Documentation for another step.
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Test_Step.md b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Test_Step.md
new file mode 100644
index 0000000000..22a6c822b8
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestRuntimeManifest/nifi-test-components-nar/META-INF/docs/step-documentation/org.example.TestConnector/Test_Step.md
@@ -0,0 +1,8 @@
+# Test Step Documentation
+
+This is test documentation for the Test Step.
+
+## Configuration
+
+Configure the test step properties as needed.
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index b10cf18a45..14472b78bd 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -654,6 +654,18 @@ public interface NiFiServiceFacade {
*/
String getAdditionalDetails(String group, String artifact, String version, String type);
+ /**
+ * Return the step documentation for the specified Connector configuration step.
+ *
+ * @param group The bundle group
+ * @param artifact The bundle artifact
+ * @param version The bundle version
+ * @param connectorType The fully qualified class name of the Connector
+ * @param stepName The name of the configuration step
+ * @return The step documentation markdown content
+ */
+ String getStepDocumentation(String group, String artifact, String version, String connectorType, String stepName);
+
/**
* Returns the list of parameter provider types.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index f9309f931d..9130a348f7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -4374,6 +4374,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return controllerFacade.getAdditionalDetails(group, artifact, version, type);
}
+ @Override
+ public String getStepDocumentation(final String group, final String artifact, final String version, final String connectorType, final String stepName) {
+ return controllerFacade.getStepDocumentation(group, artifact, version, connectorType, stepName);
+ }
+
@Override
public Set<DocumentedTypeDTO> getParameterProviderTypes(final String bundleGroup, final String bundleArtifact, final String type) {
return controllerFacade.getParameterProviderTypes(bundleGroup, bundleArtifact, type);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
index 643ff8dc61..ff48508762 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
@@ -106,6 +106,7 @@ import org.apache.nifi.web.api.entity.AboutEntity;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.AdditionalDetailsEntity;
+import org.apache.nifi.web.api.entity.StepDocumentationEntity;
import org.apache.nifi.web.api.entity.BannerEntity;
import org.apache.nifi.web.api.entity.BulletinBoardEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsForGroupRequestEntity;
@@ -2341,6 +2342,50 @@ public class FlowResource extends ApplicationResource {
return generateOkResponse(entity).build();
}
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("step-documentation/{group}/{artifact}/{version}/{connectorType}/{stepName}")
+ @Operation(
+ summary = "Retrieves the step documentation for the specified Connector configuration step.",
+ description = NON_GUARANTEED_ENDPOINT,
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = StepDocumentationEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The step documentation for the coordinates could not be located.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /flow")
+ }
+ )
+ public Response getStepDocumentation(
+ @Parameter(description = "The bundle group", required = true)
+ @PathParam("group") final String group,
+ @Parameter(description = "The bundle artifact", required = true)
+ @PathParam("artifact") final String artifact,
+ @Parameter(description = "The bundle version", required = true)
+ @PathParam("version") final String version,
+ @Parameter(description = "The fully qualified Connector type", required = true)
+ @PathParam("connectorType") final String connectorType,
+ @Parameter(description = "The configuration step name", required = true)
+ @PathParam("stepName") final String stepName
+ ) throws InterruptedException {
+
+ authorizeFlow();
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final String stepDocumentation = serviceFacade.getStepDocumentation(group, artifact, version, connectorType, stepName);
+ final StepDocumentationEntity entity = new StepDocumentationEntity();
+ entity.setStepDocumentation(stepDocumentation);
+
+ return generateOkResponse(entity).build();
+ }
+
/**
* Retrieves the types of parameter providers that this NiFi supports.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 33680eb043..72686f650c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -145,6 +145,7 @@ import org.apache.nifi.groups.RemoteProcessGroup;
import org.apache.nifi.groups.RemoteProcessGroupCounts;
import org.apache.nifi.history.History;
import org.apache.nifi.nar.ExtensionDefinition;
+import org.apache.nifi.manifest.RuntimeManifestService;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarClassLoadersHolder;
import org.apache.nifi.nar.NarManifest;
@@ -318,6 +319,7 @@ public final class DtoFactory {
private Authorizer authorizer;
private ExtensionManager extensionManager;
private ConnectorAssetRepository connectorAssetRepository;
+ private RuntimeManifestService runtimeManifestService;
public ControllerConfigurationDTO createControllerConfigurationDto(final ControllerFacade controllerFacade) {
final ControllerConfigurationDTO dto = new ControllerConfigurationDTO();
@@ -5218,6 +5220,10 @@ public final class DtoFactory {
this.connectorAssetRepository = connectorAssetRepository;
}
+ public void setRuntimeManifestService(RuntimeManifestService runtimeManifestService) {
+ this.runtimeManifestService = runtimeManifestService;
+ }
+
private ProcessingPerformanceStatusDTO createProcessingPerformanceStatusDTO(final ProcessingPerformanceStatus performanceStatus) {
final ProcessingPerformanceStatusDTO performanceStatusDTO = new ProcessingPerformanceStatusDTO();
@@ -5272,16 +5278,38 @@ public final class DtoFactory {
return null;
}
+ final BundleCoordinate bundleCoordinate = connector.getBundleCoordinate();
+ final String connectorType = connector.getCanonicalClassName();
+
+ final Set<String> stepsWithDocumentation = discoverStepsWithDocumentation(bundleCoordinate, connectorType);
+
final ConnectorConfiguration configuration = flowContext.getConfigurationContext().toConnectorConfiguration();
final ConnectorConfigurationDTO dto = new ConnectorConfigurationDTO();
final List<ConfigurationStepConfigurationDTO> configurationStepDtos = configurationSteps.stream()
- .map(step -> createConfigurationStepConfigurationDtoFromStep(step, configuration))
+ .map(step -> createConfigurationStepConfigurationDtoFromStep(step, configuration, stepsWithDocumentation))
.collect(Collectors.toList());
dto.setConfigurationStepConfigurations(configurationStepDtos);
return dto;
}
- private ConfigurationStepConfigurationDTO createConfigurationStepConfigurationDtoFromStep(final ConfigurationStep step, final ConnectorConfiguration configuration) {
+ private Set<String> discoverStepsWithDocumentation(final BundleCoordinate bundleCoordinate, final String connectorType) {
+ if (runtimeManifestService == null) {
+ return Collections.emptySet();
+ }
+
+ try {
+ final Map<String, File> stepDocs = runtimeManifestService.discoverStepDocumentation(
+ bundleCoordinate.getGroup(), bundleCoordinate.getId(), bundleCoordinate.getVersion(), connectorType);
+ return stepDocs.keySet();
+ } catch (final Exception e) {
+ logger.debug("Unable to discover step documentation for connector [{}]: {}", connectorType, e.getMessage());
+ return Collections.emptySet();
+ }
+ }
+
+ private ConfigurationStepConfigurationDTO createConfigurationStepConfigurationDtoFromStep(final ConfigurationStep step,
+ final ConnectorConfiguration configuration,
+ final Set<String> stepsWithDocumentation) {
if (step == null) {
return null;
}
@@ -5289,7 +5317,7 @@ public final class DtoFactory {
final ConfigurationStepConfigurationDTO dto = new ConfigurationStepConfigurationDTO();
dto.setConfigurationStepName(step.getName());
dto.setConfigurationStepDescription(step.getDescription());
- dto.setConfigurationStepDocumentation(step.getDocumentation());
+ dto.setDocumented(stepsWithDocumentation.contains(step.getName()));
// Get the current configuration values for this step from the flat StepConfiguration
final StepConfiguration stepConfig = configuration.getNamedStepConfigurations().stream()
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
index f51fb0c0c2..0659d8ef72 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
@@ -179,6 +179,7 @@ public class WebApplicationConfiguration {
dtoFactory.setEntityFactory(entityFactory());
dtoFactory.setExtensionManager(extensionManager);
dtoFactory.setConnectorAssetRepository(flowController.getConnectorRepository().getAssetRepository());
+ dtoFactory.setRuntimeManifestService(runtimeManifestService);
return dtoFactory;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 63611f6a1f..95d6f7f9e8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -695,6 +695,22 @@ public class ControllerFacade implements Authorizable {
}
}
+ public String getStepDocumentation(final String group, final String artifact, final String version, final String connectorType, final String stepName) {
+ final Map<String, File> stepDocsMap = runtimeManifestService.discoverStepDocumentation(group, artifact, version, connectorType);
+ final File stepDocFile = stepDocsMap.get(stepName);
+
+ if (stepDocFile == null) {
+ throw new ResourceNotFoundException("Unable to find step documentation for step [%s] in connector [%s]".formatted(stepName, connectorType));
+ }
+
+ try (final Stream<String> stepDocLines = Files.lines(stepDocFile.toPath())) {
+ return stepDocLines.collect(Collectors.joining("\n"));
+ } catch (final IOException e) {
+ throw new RuntimeException("Unable to load step documentation content for "
+ + stepDocFile.getAbsolutePath() + " due to: " + e.getMessage(), e);
+ }
+ }
+
/**
* Gets the ParameterProvider types that this controller supports.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
index 420c650620..f3c480efd5 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
@@ -18,10 +18,13 @@ package org.apache.nifi.web.dao.impl;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.connector.ConnectorAssetRepository;
+import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.components.connector.MutableConnectorConfigurationContext;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.web.NiFiCoreException;
import org.apache.nifi.web.ResourceNotFoundException;
@@ -31,6 +34,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -61,6 +65,15 @@ class StandardConnectorDAOTest {
@Mock
private ConnectorAssetRepository connectorAssetRepository;
+ @Mock
+ private FrameworkFlowContext frameworkFlowContext;
+
+ @Mock
+ private MutableConnectorConfigurationContext configurationContext;
+
+ @Mock
+ private ConnectorConfiguration connectorConfiguration;
+
private static final String CONNECTOR_ID = "test-connector-id";
private static final String STEP_NAME = "test-step";
private static final String PROPERTY_NAME = "test-property";
@@ -71,12 +84,18 @@ class StandardConnectorDAOTest {
connectorDAO.setFlowController(flowController);
when(flowController.getConnectorRepository()).thenReturn(connectorRepository);
- when(connectorRepository.getAssetRepository()).thenReturn(connectorAssetRepository);
}
@Test
void testApplyConnectorUpdate() throws Exception {
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+ when(connectorRepository.getAssetRepository()).thenReturn(connectorAssetRepository);
+ when(connectorNode.getActiveFlowContext()).thenReturn(frameworkFlowContext);
+ when(frameworkFlowContext.getConfigurationContext()).thenReturn(configurationContext);
+ when(configurationContext.toConnectorConfiguration()).thenReturn(connectorConfiguration);
+ when(connectorConfiguration.getNamedStepConfigurations()).thenReturn(Collections.emptySet());
+ when(connectorNode.getIdentifier()).thenReturn(CONNECTOR_ID);
+ when(connectorAssetRepository.getAssets(CONNECTOR_ID)).thenReturn(Collections.emptyList());
connectorDAO.applyConnectorUpdate(CONNECTOR_ID, connectorUpdateContext);
@@ -220,6 +239,8 @@ class StandardConnectorDAOTest {
@Test
void testDeleteConnectorRemovesConnectorAndAssets() {
+ when(connectorRepository.getAssetRepository()).thenReturn(connectorAssetRepository);
+
connectorDAO.deleteConnector(CONNECTOR_ID);
verify(connectorRepository).removeConnector(CONNECTOR_ID);
(nifi) 25/40: NIFI-15433: If connector validation throws an Exception keep trying u… (#10736)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit fae768c537fd31a0e623da262e443c2437cbe283
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Jan 9 16:21:18 2026 -0500
NIFI-15433: If connector validation throws an Exception keep trying u… (#10736)
* NIFI-15433: If connector validation throws an Exception keep trying until it completes (with a timeout between retries); some bug fixes
* NIFI-15433: Addressed review feedback
* NIFI-15433: Fixed failing unit tests
---
.../StandardConnectorValidationTrigger.java | 46 +++++++++++++++++++---
.../nifi/components/connector/ConnectorNode.java | 6 +++
.../connector/ConnectorValidationTrigger.java | 7 +++-
.../connector/StandardConnectorNode.java | 12 +++---
.../controller/flow/TestStandardFlowManager.java | 7 ++++
5 files changed, 66 insertions(+), 12 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationTrigger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationTrigger.java
index a88cb0aeb4..2e92671d2d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationTrigger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/StandardConnectorValidationTrigger.java
@@ -20,37 +20,73 @@ package org.apache.nifi.components.connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.concurrent.ExecutorService;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
/**
* Standard implementation of ConnectorValidationTrigger that submits validation
- * tasks to an ExecutorService for asynchronous execution.
+ * tasks to an ScheduledExecutorService for asynchronous execution.
*/
public class StandardConnectorValidationTrigger implements ConnectorValidationTrigger {
private static final Logger logger = LoggerFactory.getLogger(StandardConnectorValidationTrigger.class);
- private final ExecutorService threadPool;
+ private final ScheduledExecutorService threadPool;
private final BooleanSupplier flowInitialized;
+ private final Set<ConnectorNode> activelyValidating = Collections.synchronizedSet(new HashSet<>());
- public StandardConnectorValidationTrigger(final ExecutorService threadPool, final BooleanSupplier flowInitialized) {
+ public StandardConnectorValidationTrigger(final ScheduledExecutorService threadPool, final BooleanSupplier flowInitialized) {
this.threadPool = threadPool;
this.flowInitialized = flowInitialized;
}
@Override
public void triggerAsync(final ConnectorNode connector) {
+ // Avoid adding multiple validation tasks for the same connector concurrently. This is not 100% thread safe because when a task
+ // is rescheduled, there's a small window where a second thread could be scheduled after the Connector is removed from 'activelyValidating' and
+ // before the task is rescheduled. However, this is acceptable because having multiple threads validating concurrently is safe, it's just inefficient.
+ final boolean added = activelyValidating.add(connector);
+ if (!added) {
+ logger.debug("Connector {} is already undergoing validation; will not trigger another validation concurrently", connector);
+ return;
+ }
+
if (!flowInitialized.getAsBoolean()) {
logger.debug("Triggered to perform validation on {} asynchronously but flow is not yet initialized so will ignore validation", connector);
+ reschedule(connector, Duration.ofSeconds(1));
return;
}
- threadPool.submit(() -> trigger(connector));
+ threadPool.submit(() -> {
+ try {
+ if (connector.isValidationPaused()) {
+ logger.debug("Connector {} is currently marked as having validation paused; will retry in 1 second", connector);
+ reschedule(connector, Duration.ofSeconds(1));
+ return;
+ }
+
+ trigger(connector);
+
+ activelyValidating.remove(connector);
+ } catch (final Exception e) {
+ logger.error("Validation for connector {} failed; will retry in 5 seconds", connector, e);
+ reschedule(connector, Duration.ofSeconds(5));
+ }
+ });
}
@Override
public void trigger(final ConnectorNode connector) {
connector.performValidation();
}
+
+ private void reschedule(final ConnectorNode connector, final Duration delay) {
+ activelyValidating.remove(connector);
+ threadPool.schedule(() -> triggerAsync(connector), delay.toMillis(), TimeUnit.MILLISECONDS);
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index 2bd111c752..d4121b2db9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -113,6 +113,12 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
void resumeValidationTrigger();
+ /**
+ * Indicates whether validation triggering is currently paused.
+ * @return true if validation triggering is paused, false otherwise
+ */
+ boolean isValidationPaused();
+
List<ConfigVerificationResult> verifyConfigurationStep(String configurationStepName, StepConfiguration configurationOverrides);
List<ConfigVerificationResult> verify();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorValidationTrigger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorValidationTrigger.java
index 9cd4a13d7d..f1e617be32 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorValidationTrigger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorValidationTrigger.java
@@ -24,13 +24,18 @@ public interface ConnectorValidationTrigger {
/**
* Triggers validation of the given connector to occur asynchronously.
+ * If the Connector's validation is already in progress by another thread, this method will
+ * return without triggering another validation. If the Connector's validation is paused, this
+ * will schedule the validation to occur once unpaused.
*
* @param connector the connector to validate
*/
void triggerAsync(ConnectorNode connector);
/**
- * Triggers validation of the given connector immediately in the current thread.
+ * Triggers validation of the given connector immediately in the current thread. This will
+ * trigger validation even if other validation is currently in progress or if the Connector's
+ * validation is paused.
*
* @param connector the connector to validate
*/
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index a35e191c83..671e88efea 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -664,6 +664,11 @@ public class StandardConnectorNode implements ConnectorNode {
resetValidationState();
}
+ @Override
+ public boolean isValidationPaused() {
+ return !triggerValidation;
+ }
+
@Override
public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final StepConfiguration configurationOverrides) {
final List<SecretReference> invalidSecretRefs = new ArrayList<>();
@@ -924,12 +929,7 @@ public class StandardConnectorNode implements ConnectorNode {
private void resetValidationState() {
validationState.set(new ValidationState(ValidationStatus.VALIDATING, Collections.emptyList()));
-
- if (triggerValidation && validationTrigger != null) {
- validationTrigger.triggerAsync(this);
- } else {
- logger.debug("Reset validation state of {} but will not trigger async validation because trigger has been paused or is null", this);
- }
+ validationTrigger.triggerAsync(this);
}
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/TestStandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/TestStandardFlowManager.java
index 33ec45aac0..473962890f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/TestStandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/TestStandardFlowManager.java
@@ -23,7 +23,9 @@ import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorInitializationContext;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.ConnectorValidationTrigger;
import org.apache.nifi.components.connector.StandardConnectorInitializationContext;
+import org.apache.nifi.components.connector.StandardConnectorStateTransition;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.GarbageCollectionLog;
import org.apache.nifi.controller.MockStateManagerProvider;
@@ -54,6 +56,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -139,10 +142,14 @@ public class TestStandardFlowManager {
when(flowController.getFlowFileEventRepository()).thenReturn(mock(FlowFileEventRepository.class));
when(flowController.getReloadComponent()).thenReturn(mock(ReloadComponent.class));
+ final ConnectorValidationTrigger validationTrigger = mock(ConnectorValidationTrigger.class);
+ when(flowController.getConnectorValidationTrigger()).thenReturn(validationTrigger);
+
final ConnectorRepository connectorRepository = mock(ConnectorRepository.class);
when(connectorRepository.createInitializationContextBuilder()).thenAnswer(
invocation -> new StandardConnectorInitializationContext.Builder());
when(flowController.getConnectorRepository()).thenReturn(connectorRepository);
+ when(connectorRepository.createStateTransition(anyString(), anyString())).thenReturn(new StandardConnectorStateTransition("test component"));
// Create the connector
final String type = NopConnector.class.getName();
(nifi) 26/40: NIFI-15434: Ensure that we start or stop connectors on flow sync (#10737)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit acd54791a522e1bfee3b608f20c6465608ad116a
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Jan 9 16:44:18 2026 -0500
NIFI-15434: Ensure that we start or stop connectors on flow sync (#10737)
---
.../serialization/VersionedFlowSynchronizer.java | 17 ++++++++++++-----
.../nifi/tests/system/connectors/ConnectorCrudIT.java | 4 ++++
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
index 197800c470..000a4897a5 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
@@ -26,15 +26,15 @@ import org.apache.nifi.authorization.ManagedAuthorizer;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.cluster.protocol.DataFlow;
import org.apache.nifi.cluster.protocol.StandardDataFlow;
-import org.apache.nifi.components.connector.NamedStepConfiguration;
-import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.AssetReference;
-import org.apache.nifi.components.connector.ConnectorValueReference;
-import org.apache.nifi.components.connector.SecretReference;
-import org.apache.nifi.components.connector.StringLiteralValue;
+import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.NamedStepConfiguration;
+import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.StringLiteralValue;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.Position;
@@ -1169,6 +1169,13 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
final List<VersionedConfigurationStep> activeFlowConfig = versionedConnector.getActiveFlowConfiguration();
final List<VersionedConfigurationStep> workingFlowConfig = versionedConnector.getWorkingFlowConfiguration();
connectorRepository.inheritConfiguration(connectorNode, activeFlowConfig, workingFlowConfig, versionedConnector.getBundle());
+
+ final ScheduledState desiredState = versionedConnector.getScheduledState();
+ if (desiredState == ScheduledState.RUNNING) {
+ connectorRepository.startConnector(connectorNode);
+ } else if (desiredState == ScheduledState.ENABLED) {
+ connectorRepository.stopConnector(connectorNode);
+ }
} catch (final FlowUpdateException e) {
throw new RuntimeException(connectorNode + " failed to inherit configuration", e);
}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
index 78728935cc..fb616c6d4b 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
@@ -18,6 +18,7 @@
package org.apache.nifi.tests.system.connectors;
import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.tests.system.NiFiSystemIT;
import org.apache.nifi.toolkit.client.NiFiClientException;
import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
@@ -67,6 +68,9 @@ public class ConnectorCrudIT extends NiFiSystemIT {
final ConnectorEntity connectorAfterRestart = getNifiClient().getConnectorClient().getConnector(connector.getId());
assertNotNull(connectorAfterRestart);
+ final String connectorState = connectorAfterRestart.getComponent().getState();
+ assertEquals(ConnectorState.STOPPED.name(), connectorState);
+
final ConnectorConfigurationDTO activeConfig = connectorAfterRestart.getComponent().getActiveConfiguration();
final Map<String, ConnectorValueReferenceDTO> activeProperties = activeConfig.getConfigurationStepConfigurations().getFirst().getPropertyGroupConfigurations().getFirst().getPropertyValues();
final String activeIgnoredProperty = activeProperties.get("Ignored Property").getValue();
(nifi) 10/40: NIFI-15312: Implementation of Parameter Provider based Secrets Manager; updated mock framework to support secrets; validation cleanup (#10638)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 36a0ba24def609ff642bf2eb77c1aac11a37761f
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Dec 12 18:01:16 2025 -0500
NIFI-15312: Implementation of Parameter Provider based Secrets Manager; updated mock framework to support secrets; validation cleanup (#10638)
---
nifi-assembly/pom.xml | 12 +-
.../java/org/apache/nifi/util/NiFiProperties.java | 3 +
.../mock/connector/server/ConnectorTestRunner.java | 12 +
.../server/MockConnectorInitializationContext.java | 2 +-
.../server/StandardConnectorMockServer.java | 46 ++-
.../secrets/ConnectorTestRunnerSecretProvider.java | 86 ++++++
.../secrets/ConnectorTestRunnerSecretsManager.java | 99 +++++++
...ifi.components.connector.secrets.SecretsManager | 16 ++
.../connector/StandardConnectorTestRunner.java | 24 ++
.../src/main/resources/nifi.properties | 3 +
.../nifi/connectors/kafkas3/KafkaToS3IT.java | 78 +++--
.../secrets/ParameterProviderSecretProvider.java | 86 ++++++
.../secrets/ParameterProviderSecretsManager.java | 151 ++++++++++
.../connector/secrets/StandardSecret.java | 127 ++++++++
...andardSecretsManagerInitializationContext.java} | 21 +-
.../parameter/StandardParameterProviderNode.java | 22 +-
...ifi.components.connector.secrets.SecretsManager | 16 ++
.../TestParameterProviderSecretsManager.java | 318 +++++++++++++++++++++
.../connector/ConnectorConfiguration.java | 9 +
.../components/connector/ConnectorRepository.java | 3 +
.../ConnectorRepositoryInitializationContext.java | 3 +
.../FrameworkConnectorInitializationContext.java | 1 +
...eworkConnectorInitializationContextBuilder.java | 1 +
.../SecretProvider.java} | 16 +-
.../SecretsManager.java} | 24 +-
.../SecretsManagerInitializationContext.java} | 10 +-
.../nifi/controller/ParameterProviderNode.java | 13 +
.../StandardConnectorConfigurationContext.java | 2 +
.../StandardConnectorInitializationContext.java | 1 +
.../connector/StandardConnectorNode.java | 119 +++++++-
...StandardConnectorRepoInitializationContext.java | 9 +
.../connector/StandardConnectorRepository.java | 8 +
.../apache/nifi/controller/ExtensionBuilder.java | 6 +-
.../org/apache/nifi/controller/FlowController.java | 43 ++-
.../nifi/controller/flow/StandardFlowManager.java | 4 +-
.../TestStandardConnectorConfigurationContext.java | 1 +
.../connector/TestStandardConnectorNode.java | 1 +
37 files changed, 1296 insertions(+), 100 deletions(-)
diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index 16787bda28..1ddf8ea5b5 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -471,12 +471,6 @@ language governing permissions and limitations under the License. -->
<version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
- <dependency>
- <groupId>org.apache.nifi</groupId>
- <artifactId>nifi-kafka-to-s3-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
- <type>nar</type>
- </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-groovyx-nar</artifactId>
@@ -921,6 +915,12 @@ language governing permissions and limitations under the License. -->
<version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-to-s3-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
<!-- AspectJ library needed by the Java Agent used for native library loading (see bootstrap.conf) -->
<dependency>
<groupId>org.aspectj</groupId>
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index e70e34036b..2b354a7bc3 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -139,6 +139,9 @@ public class NiFiProperties extends ApplicationProperties {
// Connector Repository properties
public static final String CONNECTOR_REPOSITORY_IMPLEMENTATION = "nifi.components.connectors.repository.implementation";
+ // Secrets Manager properties
+ public static final String SECRETS_MANAGER_IMPLEMENTATION = "nifi.secrets.manager.implementation";
+
// security properties
public static final String SECURITY_KEYSTORE = "nifi.security.keystore";
public static final String SECURITY_KEYSTORE_TYPE = "nifi.security.keystoreType";
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
index 8dc3730229..5fe172fa78 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
@@ -18,7 +18,9 @@
package org.apache.nifi.mock.connector.server;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import java.io.Closeable;
@@ -27,6 +29,8 @@ import java.util.List;
import java.util.Map;
public interface ConnectorTestRunner extends Closeable {
+ String SECRET_PROVIDER_ID = "TestRunnerSecretsManager";
+ String SECRET_PROVIDER_NAME = "TestRunnerSecretsManager";
void applyUpdate() throws FlowUpdateException;
@@ -34,10 +38,18 @@ public interface ConnectorTestRunner extends Closeable {
void configure(String stepName, Map<String, String> propertyValues) throws FlowUpdateException;
+ void configure(String stepName, Map<String, String> propertyValues, Map<String, ConnectorValueReference> propertyReferences) throws FlowUpdateException;
+
+ SecretReference createSecretReference(String secretName);
+
ConnectorConfigVerificationResult verifyConfiguration(String stepName, Map<String, String> propertyValueOverrides);
+ ConnectorConfigVerificationResult verifyConfiguration(String stepName, Map<String, String> propertyValueOverrides, Map<String, ConnectorValueReference> referenceOverrides);
+
ConnectorConfigVerificationResult verifyConfiguration(String stepName, StepConfiguration configurationOverrides);
+ void addSecret(String name, String value);
+
void startConnector();
void stopConnector();
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
index 939035bc5e..93cb6fd44a 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
@@ -22,7 +22,7 @@ import org.apache.nifi.components.connector.FrameworkConnectorInitializationCont
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.components.connector.FrameworkConnectorInitializationContext;
import org.apache.nifi.components.connector.FrameworkFlowContext;
-import org.apache.nifi.components.connector.SecretsManager;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.flow.VersionedProcessGroup;
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
index 590e5a8049..0e5a2d86ab 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
@@ -30,9 +30,11 @@ import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StandaloneConnectorRequestReplicator;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.components.validation.DisabledServiceValidationResult;
import org.apache.nifi.components.validation.ValidationState;
@@ -48,6 +50,7 @@ import org.apache.nifi.diagnostics.DiagnosticsFactory;
import org.apache.nifi.encrypt.PropertyEncryptor;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.events.VolatileBulletinRepository;
+import org.apache.nifi.mock.connector.server.secrets.ConnectorTestRunnerSecretsManager;
import org.apache.nifi.nar.ExtensionMapping;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.reporting.BulletinRepository;
@@ -74,6 +77,7 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
private FlowController flowController;
private MockExtensionDiscoveringManager extensionManager;
private ConnectorNode connectorNode;
+ private ConnectorRepository connectorRepository;
private FlowEngine flowEngine;
private MockExtensionMapper mockExtensionMapper;
private FlowFileTransferCounts initialFlowFileTransferCounts = new FlowFileTransferCounts(0L, 0L, 0L, 0L);
@@ -113,7 +117,7 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
throw new RuntimeException("Failed to initialize FlowFile Repository", e);
}
- final ConnectorRepository connectorRepository = flowController.getConnectorRepository();
+ connectorRepository = flowController.getConnectorRepository();
if (!(connectorRepository instanceof MockConnectorRepository)) {
throw new IllegalStateException("Connector Repository is not an instance of MockConnectorRepository");
}
@@ -210,19 +214,32 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
@Override
public void configure(final String stepName, final Map<String, String> propertyValues) throws FlowUpdateException {
- final StepConfiguration configuration = toStringLiteralConfiguration(propertyValues);
- configure(stepName, configuration);
+ configure(stepName, propertyValues, Collections.emptyMap());
+ }
+
+ @Override
+ public void configure(final String stepName, final Map<String, String> propertyValues, final Map<String, ConnectorValueReference> propertyReferences) throws FlowUpdateException {
+ final StepConfiguration stepConfiguration = createStepConfiguration(propertyValues, propertyReferences);
+ configure(stepName, stepConfiguration);
}
@Override
public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final Map<String, String> propertyValueOverrides) {
- final StepConfiguration configuration = toStringLiteralConfiguration(propertyValueOverrides);
+ return verifyConfiguration(stepName, propertyValueOverrides, Collections.emptyMap());
+ }
+
+ @Override
+ public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final Map<String, String> propertyValueOverrides,
+ final Map<String, ConnectorValueReference> referenceOverrides) {
+
+ final StepConfiguration configuration = createStepConfiguration(propertyValueOverrides, referenceOverrides);
return verifyConfiguration(stepName, configuration);
}
- private StepConfiguration toStringLiteralConfiguration(final Map<String, String> propertyValues) {
- final Map<String, ConnectorValueReference> references = new HashMap<>(propertyValues.size());
- propertyValues.forEach((key, value) -> references.put(key, new StringLiteralValue(value)));
+ private StepConfiguration createStepConfiguration(final Map<String, String> propertyValues, final Map<String, ConnectorValueReference> propertyReferences) {
+ final Map<String, ConnectorValueReference> references = new HashMap<>();
+ propertyValues.forEach((key, value) -> references.put(key, value == null ? null : new StringLiteralValue(value)));
+ references.putAll(propertyReferences);
return new StepConfiguration(references);
}
@@ -232,6 +249,21 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
return new MockServerConfigVerificationResult(results);
}
+ @Override
+ public void addSecret(final String name, final String value) {
+ final SecretsManager secretsManager = connectorRepository.getSecretsManager();
+ if (!(secretsManager instanceof final ConnectorTestRunnerSecretsManager testRunnerSecretsManager)) {
+ throw new IllegalStateException("Secrets Manager is not an instance of ConnectorTestRunnerSecretsManager");
+ }
+
+ testRunnerSecretsManager.addSecret(name, value);
+ }
+
+ @Override
+ public SecretReference createSecretReference(final String secretName) {
+ return new SecretReference(ConnectorTestRunner.SECRET_PROVIDER_ID, ConnectorTestRunner.SECRET_PROVIDER_NAME, secretName);
+ }
+
@Override
public void startConnector() {
initialFlowFileTransferCounts = connectorNode.getFlowFileTransferCounts();
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java
new file mode 100644
index 0000000000..16e05ff4f0
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretProvider.java
@@ -0,0 +1,86 @@
+/*
+ * 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.mock.connector.server.secrets;
+
+import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.components.connector.secrets.SecretProvider;
+import org.apache.nifi.components.connector.secrets.StandardSecret;
+import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ConnectorTestRunnerSecretProvider implements SecretProvider {
+ public static final String GROUP_NAME = "Default";
+
+ private final Map<String, String> secrets = new HashMap<>();
+
+ public void addSecret(final String key, final String value) {
+ this.secrets.put(key, value);
+ }
+
+ @Override
+ public String getProviderId() {
+ return ConnectorTestRunner.SECRET_PROVIDER_ID;
+ }
+
+ @Override
+ public String getProviderName() {
+ return ConnectorTestRunner.SECRET_PROVIDER_ID;
+ }
+
+ @Override
+ public List<Secret> getAllSecrets() {
+ final List<Secret> secrets = new ArrayList<>();
+ for (final Map.Entry<String, String> entry : this.secrets.entrySet()) {
+ final Secret secret = new StandardSecret.Builder()
+ .providerName(ConnectorTestRunner.SECRET_PROVIDER_NAME)
+ .groupName(GROUP_NAME)
+ .name(entry.getKey())
+ .value(entry.getValue())
+ .build();
+
+ secrets.add(secret);
+ }
+
+ return secrets;
+ }
+
+ @Override
+ public List<Secret> getSecrets(final List<String> fullyQualifiedSecretNames) {
+ final List<Secret> matchingSecrets = new ArrayList<>();
+
+ for (final String secretName : fullyQualifiedSecretNames) {
+ final String value = secrets.get(secretName);
+
+ if (value != null) {
+ final Secret secret = new StandardSecret.Builder()
+ .providerName(ConnectorTestRunner.SECRET_PROVIDER_NAME)
+ .groupName(GROUP_NAME)
+ .name(secretName)
+ .value(value)
+ .build();
+
+ matchingSecrets.add(secret);
+ }
+ }
+ return matchingSecrets;
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretsManager.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretsManager.java
new file mode 100644
index 0000000000..8af0f6a8b9
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/secrets/ConnectorTestRunnerSecretsManager.java
@@ -0,0 +1,99 @@
+/*
+ * 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.mock.connector.server.secrets;
+
+import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.secrets.SecretProvider;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
+import org.apache.nifi.components.connector.secrets.SecretsManagerInitializationContext;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+public class ConnectorTestRunnerSecretsManager implements SecretsManager {
+ private final ConnectorTestRunnerSecretProvider secretProvider = new ConnectorTestRunnerSecretProvider();
+
+ @Override
+ public void initialize(final SecretsManagerInitializationContext initializationContext) {
+ }
+
+ public void addSecret(final String name, final String value) {
+ secretProvider.addSecret(name, value);
+ }
+
+ @Override
+ public List<Secret> getAllSecrets() {
+ return secretProvider.getAllSecrets();
+ }
+
+ @Override
+ public Set<SecretProvider> getSecretProviders() {
+ return Set.of(secretProvider);
+ }
+
+ @Override
+ public Optional<Secret> getSecret(final SecretReference secretReference) {
+ // Check that appropriate provider given
+ final SecretProvider provider = getProvider(secretReference);
+ if (provider == null) {
+ return Optional.empty();
+ }
+
+ final List<Secret> secrets = provider.getSecrets(List.of(secretReference.getSecretName()));
+ return secrets.isEmpty() ? Optional.empty() : Optional.of(secrets.getFirst());
+ }
+
+ private SecretProvider getProvider(final SecretReference secretReference) {
+ final String providerId = secretReference.getProviderId();
+ if (providerId != null) {
+ for (final SecretProvider provider : getSecretProviders()) {
+ if (provider.getProviderId().equals(providerId)) {
+ return provider;
+ }
+ }
+
+ return null;
+ }
+
+ final String providerName = secretReference.getProviderName();
+ if (providerName != null) {
+ for (final SecretProvider provider : getSecretProviders()) {
+ if (provider.getProviderName().equals(providerName)) {
+ return provider;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public Map<SecretReference, Secret> getSecrets(final Set<SecretReference> secretReferences) {
+ final Map<SecretReference, Secret> secrets = new HashMap<>();
+ for (final SecretReference reference : secretReferences) {
+ final Secret secret = getSecret(reference).orElse(null);
+ secrets.put(reference, secret);
+ }
+
+ return secrets;
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.components.connector.secrets.SecretsManager b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.components.connector.secrets.SecretsManager
new file mode 100644
index 0000000000..a1c0de54d1
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.components.connector.secrets.SecretsManager
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.nifi.mock.connector.server.secrets.ConnectorTestRunnerSecretsManager
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
index 5cf5d5ef70..71636a5af1 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
@@ -20,7 +20,9 @@ package org.apache.nifi.mock.connector;
import org.apache.nifi.NiFiServer;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
import org.apache.nifi.mock.connector.server.ConnectorMockServer;
@@ -131,16 +133,38 @@ public class StandardConnectorTestRunner implements ConnectorTestRunner, Closeab
mockServer.configure(stepName, propertyValues);
}
+ @Override
+ public void configure(final String stepName, final Map<String, String> propertyValues, final Map<String, ConnectorValueReference> propertyReferences) throws FlowUpdateException {
+ mockServer.configure(stepName, propertyValues, propertyReferences);
+ }
+
+ @Override
+ public SecretReference createSecretReference(final String secretName) {
+ return mockServer.createSecretReference(secretName);
+ }
+
@Override
public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final Map<String, String> propertyValueOverrides) {
return mockServer.verifyConfiguration(stepName, propertyValueOverrides);
}
+ @Override
+ public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final Map<String, String> propertyValueOverrides,
+ final Map<String, ConnectorValueReference> referenceOverrides) {
+
+ return mockServer.verifyConfiguration(stepName, propertyValueOverrides, referenceOverrides);
+ }
+
@Override
public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final StepConfiguration configurationOverrides) {
return mockServer.verifyConfiguration(stepName, configurationOverrides);
}
+ @Override
+ public void addSecret(final String name, final String value) {
+ mockServer.addSecret(name, value);
+ }
+
@Override
public void startConnector() {
mockServer.startConnector();
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties
index 046a2980b2..071f1cca3c 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties
@@ -94,6 +94,9 @@ nifi.nar.persistence.provider.properties.directory=target/nifi-storage/nar_repos
# Asset Management
nifi.asset.manager.properties.directory=target/nifi-storage/assets
+# Secrets Manager
+nifi.secrets.manager.implementation=org.apache.nifi.mock.connector.server.secrets.ConnectorTestRunnerSecretsManager
+
# Site to Site properties
nifi.remote.input.host=localhost
nifi.remote.input.secure=true
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java
index 6ccdc34fd9..f715ea2e4e 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java
@@ -32,6 +32,7 @@ import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.serialization.StringSerializer;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
@@ -299,18 +300,19 @@ public class KafkaToS3IT {
"Kafka Brokers", "localhost:9093",
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
- "Username", SCRAM_USERNAME,
- "Password", SCRAM_PASSWORD
+ "Username", SCRAM_USERNAME
+ );
+ runner.addSecret("kafka-password", SCRAM_PASSWORD);
+ final Map<String, ConnectorValueReference> kafkaServerSecretRefs = Map.of(
+ "Password", runner.createSecretReference("kafka-password")
);
-
- runner.applyUpdate();
// Perform verification to ensure that valid server configuration passes
- final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", kafkaServerConfig);
+ final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", kafkaServerConfig, kafkaServerSecretRefs);
connectionVerificationResults.assertNoFailures();
// Apply the configuration that we've now validated
- runner.configure("Kafka Connection", kafkaServerConfig);
+ runner.configure("Kafka Connection", kafkaServerConfig, kafkaServerSecretRefs);
// Perform verification to ensure that valid topic configuration passes
final Map<String, String> topic1Config = Map.of(
@@ -360,8 +362,11 @@ public class KafkaToS3IT {
"Kafka Brokers", "localhost:9093",
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
- "Username", SCRAM_USERNAME,
- "Password", SCRAM_PASSWORD
+ "Username", SCRAM_USERNAME
+ );
+ runner.addSecret("kafka-password", SCRAM_PASSWORD);
+ final Map<String, ConnectorValueReference> kafkaSecretRefs = Map.of(
+ "Password", runner.createSecretReference("kafka-password")
);
final Map<String, String> kafkaTopicConfig = Map.of(
@@ -378,14 +383,17 @@ public class KafkaToS3IT {
Map.entry("S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()),
Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
- Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
Map.entry("Target Object Size", "1 MB"),
Map.entry("Merge Latency", "1 sec")
);
+ runner.addSecret("s3-secret-key", localStackContainer.getSecretKey());
+ final Map<String, ConnectorValueReference> s3SecretRefs = Map.of(
+ "S3 Secret Access Key", runner.createSecretReference("s3-secret-key")
+ );
- runner.configure("Kafka Connection", kafkaServerConfig);
+ runner.configure("Kafka Connection", kafkaServerConfig, kafkaSecretRefs);
runner.configure("Kafka Topics", kafkaTopicConfig);
- runner.configure("S3 Configuration", s3Config);
+ runner.configure("S3 Configuration", s3Config, s3SecretRefs);
runner.applyUpdate();
final List<ValidationResult> validationResults = runner.validate();
@@ -449,14 +457,17 @@ public class KafkaToS3IT {
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
"Username", SCRAM_USERNAME,
- "Password", SCRAM_PASSWORD,
"Schema Registry URL", getSchemaRegistryUrl()
);
+ runner.addSecret("kafka-password", SCRAM_PASSWORD);
+ final Map<String, ConnectorValueReference> kafkaSecretRefs = Map.of(
+ "Password", runner.createSecretReference("kafka-password")
+ );
- final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", kafkaConnectionConfig);
+ final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", kafkaConnectionConfig, kafkaSecretRefs);
connectionVerificationResults.assertNoFailures();
- runner.configure("Kafka Connection", kafkaConnectionConfig);
+ runner.configure("Kafka Connection", kafkaConnectionConfig, kafkaSecretRefs);
final Map<String, String> avroTopicConfig = Map.of(
"Topic Names", "avro-topic",
@@ -517,9 +528,12 @@ public class KafkaToS3IT {
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
"Username", SCRAM_USERNAME,
- "Password", SCRAM_PASSWORD,
"Schema Registry URL", getSchemaRegistryUrl()
);
+ runner.addSecret("kafka-password", SCRAM_PASSWORD);
+ final Map<String, ConnectorValueReference> kafkaSecretRefs = Map.of(
+ "Password", runner.createSecretReference("kafka-password")
+ );
final Map<String, String> kafkaTopicConfig = Map.of(
"Topic Names", "user-events",
@@ -535,14 +549,17 @@ public class KafkaToS3IT {
Map.entry("S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()),
Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
- Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
Map.entry("Target Object Size", "1 MB"),
Map.entry("Merge Latency", "1 sec")
);
+ runner.addSecret("s3-secret-key", localStackContainer.getSecretKey());
+ final Map<String, ConnectorValueReference> s3SecretRefs = Map.of(
+ "S3 Secret Access Key", runner.createSecretReference("s3-secret-key")
+ );
- runner.configure("Kafka Connection", kafkaConnectionConfig);
+ runner.configure("Kafka Connection", kafkaConnectionConfig, kafkaSecretRefs);
runner.configure("Kafka Topics", kafkaTopicConfig);
- runner.configure("S3 Configuration", s3Config);
+ runner.configure("S3 Configuration", s3Config, s3SecretRefs);
runner.applyUpdate();
final List<ValidationResult> validationResults = runner.validate();
@@ -613,8 +630,11 @@ public class KafkaToS3IT {
"Kafka Brokers", "localhost:9093",
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
- "Username", SCRAM_USERNAME,
- "Password", SCRAM_PASSWORD
+ "Username", SCRAM_USERNAME
+ );
+ runner.addSecret("kafka-password", SCRAM_PASSWORD);
+ final Map<String, ConnectorValueReference> kafkaSecretRefs = Map.of(
+ "Password", runner.createSecretReference("kafka-password")
);
final Map<String, String> jsonTopicConfig = Map.of(
@@ -631,14 +651,17 @@ public class KafkaToS3IT {
Map.entry("S3 Endpoint Override URL", "http://invalid-s3-endpoint:9999"),
Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
- Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
Map.entry("Target Object Size", "1 MB"),
Map.entry("Merge Latency", "1 sec")
);
+ runner.addSecret("s3-secret-key", localStackContainer.getSecretKey());
+ final Map<String, ConnectorValueReference> s3SecretRefs = Map.of(
+ "S3 Secret Access Key", runner.createSecretReference("s3-secret-key")
+ );
- runner.configure("Kafka Connection", kafkaServerConfig);
+ runner.configure("Kafka Connection", kafkaServerConfig, kafkaSecretRefs);
runner.configure("Kafka Topics", jsonTopicConfig);
- runner.configure("S3 Configuration", s3InvalidConfig);
+ runner.configure("S3 Configuration", s3InvalidConfig, s3SecretRefs);
runner.applyUpdate();
// Run the Connector with the invalid S3 endpoint to queue the JSON data. Wait for data to be queued up.
@@ -658,12 +681,11 @@ public class KafkaToS3IT {
Map.entry("S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()),
Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
- Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
Map.entry("Target Object Size", "1 MB"),
Map.entry("Merge Latency", "1 sec")
);
- runner.configure("S3 Configuration", s3ValidJsonConfig);
+ runner.configure("S3 Configuration", s3ValidJsonConfig, s3SecretRefs);
runner.applyUpdate();
// Make sure there is no data in S3 yet.
@@ -679,7 +701,6 @@ public class KafkaToS3IT {
Map.entry("S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()),
Map.entry("S3 Authentication Strategy", "Access Key ID and Secret Key"),
Map.entry("S3 Access Key ID", localStackContainer.getAccessKey()),
- Map.entry("S3 Secret Access Key", localStackContainer.getSecretKey()),
Map.entry("Target Object Size", "1 MB"),
Map.entry("Merge Latency", "1 sec")
);
@@ -697,13 +718,12 @@ public class KafkaToS3IT {
"Security Protocol", "SASL_PLAINTEXT",
"SASL Mechanism", "PLAIN",
"Username", SCRAM_USERNAME,
- "Password", SCRAM_PASSWORD,
"Schema Registry URL", getSchemaRegistryUrl()
);
- runner.configure("Kafka Connection", kafkaConnectionWithSchemaRegistry);
+ runner.configure("Kafka Connection", kafkaConnectionWithSchemaRegistry, kafkaSecretRefs);
runner.configure("Kafka Topics", avroTopicConfig);
- runner.configure("S3 Configuration", s3ValidAvroConfig);
+ runner.configure("S3 Configuration", s3ValidAvroConfig, s3SecretRefs);
runner.applyUpdate();
// After draining, there should be one JSON file in S3.
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java
new file mode 100644
index 0000000000..2f058f3c2a
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretProvider.java
@@ -0,0 +1,86 @@
+/*
+ * 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.components.connector.secrets;
+
+import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.controller.ParameterProviderNode;
+import org.apache.nifi.parameter.Parameter;
+import org.apache.nifi.parameter.ParameterDescriptor;
+import org.apache.nifi.parameter.ParameterGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ParameterProviderSecretProvider implements SecretProvider {
+ private final ParameterProviderNode parameterProvider;
+
+ public ParameterProviderSecretProvider(final ParameterProviderNode parameterProvider) {
+ this.parameterProvider = parameterProvider;
+ }
+
+ @Override
+ public String getProviderId() {
+ return parameterProvider.getIdentifier();
+ }
+
+ @Override
+ public String getProviderName() {
+ return parameterProvider.getName();
+ }
+
+ @Override
+ public List<Secret> getAllSecrets() {
+ final List<Secret> secrets = new ArrayList<>();
+
+ final List<ParameterGroup> parameterGroups = parameterProvider.fetchParameterValues();
+ for (final ParameterGroup group : parameterGroups) {
+ for (final Parameter parameter : group.getParameters()) {
+ final Secret secret = createSecret(group.getGroupName(), parameter);
+ secrets.add(secret);
+ }
+ }
+
+ return secrets;
+ }
+
+ private Secret createSecret(final String groupName, final Parameter parameter) {
+ final ParameterDescriptor descriptor = parameter.getDescriptor();
+
+ return new StandardSecret.Builder()
+ .providerName(getProviderName())
+ .groupName(groupName)
+ .name(descriptor.getName())
+ .description(descriptor.getDescription())
+ .value(parameter.getValue())
+ .build();
+ }
+
+ @Override
+ public List<Secret> getSecrets(final List<String> fullyQualifiedSecretNames) {
+ final List<ParameterGroup> parameterGroups = parameterProvider.fetchParameterValues(fullyQualifiedSecretNames);
+ final List<Secret> secrets = new ArrayList<>();
+ for (final ParameterGroup group : parameterGroups) {
+ for (final Parameter parameter : group.getParameters()) {
+ final Secret secret = createSecret(group.getGroupName(), parameter);
+ secrets.add(secret);
+ }
+ }
+
+ return secrets;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
new file mode 100644
index 0000000000..50919c8b88
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
@@ -0,0 +1,151 @@
+/*
+ * 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.components.connector.secrets;
+
+import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.controller.ParameterProviderNode;
+import org.apache.nifi.controller.flow.FlowManager;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class ParameterProviderSecretsManager implements SecretsManager {
+ private FlowManager flowManager;
+
+ @Override
+ public void initialize(final SecretsManagerInitializationContext initializationContext) {
+ this.flowManager = initializationContext.getFlowManager();
+ }
+
+ @Override
+ public List<Secret> getAllSecrets() {
+ final List<Secret> secrets = new ArrayList<>();
+ for (final SecretProvider provider : getSecretProviders()) {
+ secrets.addAll(provider.getAllSecrets());
+ }
+
+ // Sort secrets by Provider Name, then Group Name, then Secret Name
+ secrets.sort(Comparator.comparing(Secret::getProviderName)
+ .thenComparing(Secret::getGroupName)
+ .thenComparing(Secret::getName));
+
+ return secrets;
+ }
+
+ @Override
+ public Set<SecretProvider> getSecretProviders() {
+ final Set<SecretProvider> providers = new HashSet<>();
+ for (final ParameterProviderNode parameterProviderNode : flowManager.getAllParameterProviders()) {
+ providers.add(new ParameterProviderSecretProvider(parameterProviderNode));
+ }
+
+ return providers;
+ }
+
+ @Override
+ public Optional<Secret> getSecret(final SecretReference secretReference) {
+ final SecretProvider provider = findProvider(secretReference);
+ if (provider == null) {
+ return Optional.empty();
+ }
+
+ final List<Secret> secrets = provider.getSecrets(List.of(secretReference.getSecretName()));
+ if (secrets.isEmpty()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(secrets.getFirst());
+ }
+
+ @Override
+ public Map<SecretReference, Secret> getSecrets(final Set<SecretReference> secretReferences) {
+ // Partition secret references by Provider
+ final Map<SecretProvider, Set<SecretReference>> referencesByProvider = new HashMap<>();
+ for (final SecretReference secretReference : secretReferences) {
+ final SecretProvider provider = findProvider(secretReference);
+ referencesByProvider.computeIfAbsent(provider, k -> new HashSet<>()).add(secretReference);
+ }
+
+ final Map<SecretReference, Secret> secrets = new HashMap<>();
+ for (final Map.Entry<SecretProvider, Set<SecretReference>> entry : referencesByProvider.entrySet()) {
+ final SecretProvider provider = entry.getKey();
+ final Set<SecretReference> references = entry.getValue();
+
+ // If no provider found, be sure to map to a null Secret rather than skipping
+ if (provider == null) {
+ for (final SecretReference secretReference : references) {
+ secrets.put(secretReference, null);
+ }
+
+ continue;
+ }
+
+ final List<String> secretNames = new ArrayList<>();
+ references.forEach(ref -> secretNames.add(ref.getSecretName()));
+ final List<Secret> retrievedSecrets = provider.getSecrets(secretNames);
+ final Map<String, Secret> secretsByName = retrievedSecrets.stream()
+ .collect(Collectors.toMap(Secret::getName, Function.identity()));
+
+ for (final SecretReference secretReference : references) {
+ final Secret secret = secretsByName.get(secretReference.getSecretName());
+ secrets.put(secretReference, secret);
+ }
+ }
+
+ return secrets;
+ }
+
+ private SecretProvider findProvider(final SecretReference secretReference) {
+ final Set<SecretProvider> providers = getSecretProviders();
+
+ // Search first by Provider ID, if it's provided.
+ final String providerId = secretReference.getProviderId();
+ if (providerId != null) {
+ for (final SecretProvider provider : providers) {
+ if (providerId.equals(provider.getProviderId())) {
+ return provider;
+ }
+ }
+
+ // If ID is provided but doesn't match, do not consider name.
+ return null;
+ }
+
+ // No Provider found by ID, search by Provider Name
+ final String providerName = secretReference.getProviderName();
+ if (providerName != null) {
+ for (final SecretProvider provider : providers) {
+ if (providerName.equals(provider.getProviderName())) {
+ return provider;
+ }
+ }
+ }
+
+ // No Provider found
+ return null;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java
new file mode 100644
index 0000000000..443b19cd06
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecret.java
@@ -0,0 +1,127 @@
+/*
+ * 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.components.connector.secrets;
+
+import org.apache.nifi.components.connector.Secret;
+
+import java.util.Objects;
+
+public class StandardSecret implements Secret {
+ private final String providerName;
+ private final String groupName;
+ private final String name;
+ private final String description;
+ private final String value;
+
+ private StandardSecret(final Builder builder) {
+ this.providerName = builder.providerName;
+ this.groupName = builder.groupName;
+ this.name = builder.name;
+ this.description = builder.description;
+ this.value = builder.value;
+ }
+
+ @Override
+ public String getProviderName() {
+ return providerName;
+ }
+
+ @Override
+ public String getGroupName() {
+ return groupName;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "StandardSecret[providerName=%s, groupName=%s, name=%s, description=%s]".formatted(
+ providerName, groupName, name, description);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(providerName, groupName, name, description);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final StandardSecret other = (StandardSecret) obj;
+ return Objects.equals(this.providerName, other.providerName)
+ && Objects.equals(this.groupName, other.groupName)
+ && Objects.equals(this.name, other.name)
+ && Objects.equals(this.description, other.description);
+ }
+
+ public static class Builder {
+ private String providerName;
+ private String groupName;
+ private String name;
+ private String description;
+ private String value;
+
+ public Builder providerName(String providerName) {
+ this.providerName = providerName;
+ return this;
+ }
+
+ public Builder groupName(String groupName) {
+ this.groupName = groupName;
+ return this;
+ }
+
+ public Builder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder value(String value) {
+ this.value = value;
+ return this;
+ }
+
+ public StandardSecret build() {
+ return new StandardSecret(this);
+ }
+ }
+
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecretsManagerInitializationContext.java
similarity index 66%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
copy to nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecretsManagerInitializationContext.java
index 5b0b923ac7..ae5013768a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/StandardSecretsManagerInitializationContext.java
@@ -15,20 +15,19 @@
* limitations under the License.
*/
-package org.apache.nifi.components.connector;
+package org.apache.nifi.components.connector.secrets;
-import org.apache.nifi.controller.NodeTypeProvider;
import org.apache.nifi.controller.flow.FlowManager;
-import org.apache.nifi.nar.ExtensionManager;
-public interface ConnectorRepositoryInitializationContext {
+public class StandardSecretsManagerInitializationContext implements SecretsManagerInitializationContext {
+ private final FlowManager flowManager;
- FlowManager getFlowManager();
-
- ExtensionManager getExtensionManager();
-
- NodeTypeProvider getNodeTypeProvider();
-
- ConnectorRequestReplicator getRequestReplicator();
+ public StandardSecretsManagerInitializationContext(final FlowManager flowManager) {
+ this.flowManager = flowManager;
+ }
+ @Override
+ public FlowManager getFlowManager() {
+ return flowManager;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/parameter/StandardParameterProviderNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/parameter/StandardParameterProviderNode.java
index ee2a47a6c7..b3b14565b1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/parameter/StandardParameterProviderNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/parameter/StandardParameterProviderNode.java
@@ -276,16 +276,32 @@ public class StandardParameterProviderNode extends AbstractComponentNode impleme
}
@Override
- public void fetchParameters() {
+ public List<ParameterGroup> fetchParameterValues() {
final ParameterProvider parameterProvider = parameterProviderRef.get().getParameterProvider();
final ConfigurationContext configurationContext = getConfigurationContext();
- List<ParameterGroup> fetchedParameterGroups;
+
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(getExtensionManager(), parameterProvider.getClass(), parameterProvider.getIdentifier())) {
- fetchedParameterGroups = parameterProvider.fetchParameters(configurationContext);
+ return parameterProvider.fetchParameters(configurationContext);
} catch (final IOException | RuntimeException e) {
throw new IllegalStateException(String.format("Error fetching parameters for %s: %s", this, e.getMessage()), e);
}
+ }
+
+ @Override
+ public List<ParameterGroup> fetchParameterValues(final List<String> fullyQualifiedParameterNames) {
+ final ParameterProvider parameterProvider = parameterProviderRef.get().getParameterProvider();
+ final ConfigurationContext configurationContext = getConfigurationContext();
+ try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(getExtensionManager(), parameterProvider.getClass(), parameterProvider.getIdentifier())) {
+ return parameterProvider.fetchParameters(configurationContext, fullyQualifiedParameterNames);
+ } catch (final IOException | RuntimeException e) {
+ throw new IllegalStateException(String.format("Error fetching parameters for %s: %s", this, e.getMessage()), e);
+ }
+ }
+
+ @Override
+ public void fetchParameters() {
+ final List<ParameterGroup> fetchedParameterGroups = fetchParameterValues();
if (fetchedParameterGroups == null || fetchedParameterGroups.isEmpty()) {
return;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/resources/META-INF/services/org.apache.nifi.components.connector.secrets.SecretsManager b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/resources/META-INF/services/org.apache.nifi.components.connector.secrets.SecretsManager
new file mode 100644
index 0000000000..082fffbd8f
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/resources/META-INF/services/org.apache.nifi.components.connector.secrets.SecretsManager
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.nifi.components.connector.secrets.ParameterProviderSecretsManager
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java
new file mode 100644
index 0000000000..51a50973ef
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java
@@ -0,0 +1,318 @@
+/*
+ * 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.components.connector.secrets;
+
+import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.controller.ParameterProviderNode;
+import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.parameter.Parameter;
+import org.apache.nifi.parameter.ParameterDescriptor;
+import org.apache.nifi.parameter.ParameterGroup;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TestParameterProviderSecretsManager {
+
+ private static final String PROVIDER_1_ID = "provider-1-id";
+ private static final String PROVIDER_1_NAME = "Provider One";
+ private static final String PROVIDER_2_ID = "provider-2-id";
+ private static final String PROVIDER_2_NAME = "Provider Two";
+
+ private static final String GROUP_1_NAME = "Group One";
+ private static final String GROUP_2_NAME = "Group Two";
+
+ private static final String SECRET_1_NAME = "secret-one";
+ private static final String SECRET_1_DESCRIPTION = "First secret";
+ private static final String SECRET_1_VALUE = "secret-value-one";
+
+ private static final String SECRET_2_NAME = "secret-two";
+ private static final String SECRET_2_DESCRIPTION = "Second secret";
+ private static final String SECRET_2_VALUE = "secret-value-two";
+
+ private static final String SECRET_3_NAME = "secret-three";
+ private static final String SECRET_3_DESCRIPTION = "Third secret";
+ private static final String SECRET_3_VALUE = "secret-value-three";
+
+ private ParameterProviderSecretsManager secretsManager;
+
+ @BeforeEach
+ public void setup() {
+ final FlowManager flowManager = mock(FlowManager.class);
+ final ParameterProviderNode providerNode1 = createMockedParameterProviderNode(PROVIDER_1_ID, PROVIDER_1_NAME, GROUP_1_NAME,
+ createParameter(SECRET_1_NAME, SECRET_1_DESCRIPTION, SECRET_1_VALUE),
+ createParameter(SECRET_2_NAME, SECRET_2_DESCRIPTION, SECRET_2_VALUE));
+ final ParameterProviderNode providerNode2 = createMockedParameterProviderNode(PROVIDER_2_ID, PROVIDER_2_NAME, GROUP_2_NAME,
+ createParameter(SECRET_3_NAME, SECRET_3_DESCRIPTION, SECRET_3_VALUE));
+
+ final Set<ParameterProviderNode> providers = new HashSet<>();
+ providers.add(providerNode1);
+ providers.add(providerNode2);
+ when(flowManager.getAllParameterProviders()).thenReturn(providers);
+
+ secretsManager = new ParameterProviderSecretsManager();
+ final SecretsManagerInitializationContext initContext = new StandardSecretsManagerInitializationContext(flowManager);
+ secretsManager.initialize(initContext);
+ }
+
+ private ParameterProviderNode createMockedParameterProviderNode(final String id, final String name, final String groupName, final Parameter... parameters) {
+ final ParameterProviderNode node = mock(ParameterProviderNode.class);
+ when(node.getIdentifier()).thenReturn(id);
+ when(node.getName()).thenReturn(name);
+
+ final List<Parameter> parameterList = List.of(parameters);
+ final ParameterGroup group = new ParameterGroup(groupName, parameterList);
+ final List<ParameterGroup> groups = List.of(group);
+
+ when(node.fetchParameterValues()).thenReturn(groups);
+
+ when(node.fetchParameterValues(anyList())).thenAnswer(invocation -> {
+ final List<String> requestedNames = invocation.getArgument(0);
+ final List<Parameter> matchingParameters = parameterList.stream()
+ .filter(p -> requestedNames.contains(p.getDescriptor().getName()))
+ .toList();
+ if (matchingParameters.isEmpty()) {
+ return List.of();
+ }
+ return List.of(new ParameterGroup(groupName, matchingParameters));
+ });
+
+ return node;
+ }
+
+ private Parameter createParameter(final String name, final String description, final String value) {
+ final ParameterDescriptor descriptor = new ParameterDescriptor.Builder()
+ .name(name)
+ .description(description)
+ .build();
+ return new Parameter.Builder()
+ .descriptor(descriptor)
+ .value(value)
+ .build();
+ }
+
+ private SecretReference createSecretReference(final String providerId, final String providerName, final String secretName) {
+ final SecretReference reference = mock(SecretReference.class);
+ when(reference.getProviderId()).thenReturn(providerId);
+ when(reference.getProviderName()).thenReturn(providerName);
+ when(reference.getSecretName()).thenReturn(secretName);
+ return reference;
+ }
+
+ @Test
+ public void testGetSecretProvidersReturnsOneProviderPerMockedParameterProvider() {
+ final Set<SecretProvider> providers = secretsManager.getSecretProviders();
+
+ assertEquals(2, providers.size());
+
+ boolean foundProvider1 = false;
+ boolean foundProvider2 = false;
+ for (final SecretProvider provider : providers) {
+ if (PROVIDER_1_ID.equals(provider.getProviderId())) {
+ assertEquals(PROVIDER_1_NAME, provider.getProviderName());
+ foundProvider1 = true;
+ } else if (PROVIDER_2_ID.equals(provider.getProviderId())) {
+ assertEquals(PROVIDER_2_NAME, provider.getProviderName());
+ foundProvider2 = true;
+ }
+ }
+
+ assertTrue(foundProvider1);
+ assertTrue(foundProvider2);
+ }
+
+ @Test
+ public void testGetAllSecretsRetrievesSecretsFromAllProviders() {
+ final List<Secret> allSecrets = secretsManager.getAllSecrets();
+
+ assertEquals(3, allSecrets.size());
+
+ boolean foundSecret1 = false;
+ boolean foundSecret2 = false;
+ boolean foundSecret3 = false;
+
+ for (final Secret secret : allSecrets) {
+ if (SECRET_1_NAME.equals(secret.getName())) {
+ assertEquals(PROVIDER_1_NAME, secret.getProviderName());
+ assertEquals(GROUP_1_NAME, secret.getGroupName());
+ assertEquals(SECRET_1_VALUE, secret.getValue());
+ foundSecret1 = true;
+ } else if (SECRET_2_NAME.equals(secret.getName())) {
+ assertEquals(PROVIDER_1_NAME, secret.getProviderName());
+ assertEquals(GROUP_1_NAME, secret.getGroupName());
+ assertEquals(SECRET_2_VALUE, secret.getValue());
+ foundSecret2 = true;
+ } else if (SECRET_3_NAME.equals(secret.getName())) {
+ assertEquals(PROVIDER_2_NAME, secret.getProviderName());
+ assertEquals(GROUP_2_NAME, secret.getGroupName());
+ assertEquals(SECRET_3_VALUE, secret.getValue());
+ foundSecret3 = true;
+ }
+ }
+
+ assertTrue(foundSecret1);
+ assertTrue(foundSecret2);
+ assertTrue(foundSecret3);
+ }
+
+ @Test
+ public void testGetSecretReturnsPopulatedOptionalWhenSecretIsFoundById() {
+ final SecretReference reference = createSecretReference(PROVIDER_1_ID, null, SECRET_1_NAME);
+
+ final Optional<Secret> result = secretsManager.getSecret(reference);
+
+ assertTrue(result.isPresent());
+ final Secret secret = result.get();
+ assertEquals(SECRET_1_NAME, secret.getName());
+ assertEquals(SECRET_1_VALUE, secret.getValue());
+ assertEquals(PROVIDER_1_NAME, secret.getProviderName());
+ }
+
+ @Test
+ public void testGetSecretReturnsPopulatedOptionalWhenSecretIsFoundByName() {
+ final SecretReference reference = createSecretReference(null, PROVIDER_2_NAME, SECRET_3_NAME);
+
+ final Optional<Secret> result = secretsManager.getSecret(reference);
+
+ assertTrue(result.isPresent());
+ final Secret secret = result.get();
+ assertEquals(SECRET_3_NAME, secret.getName());
+ assertEquals(SECRET_3_VALUE, secret.getValue());
+ assertEquals(PROVIDER_2_NAME, secret.getProviderName());
+ }
+
+ @Test
+ public void testGetSecretReturnsEmptyOptionalWhenInvalidSecretNameProvided() {
+ final SecretReference reference = createSecretReference(PROVIDER_1_ID, PROVIDER_1_NAME, "non-existent-secret");
+ final Optional<Secret> result = secretsManager.getSecret(reference);
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ public void testGetSecretReturnsEmptyOptionalWhenProviderNotFound() {
+ final SecretReference reference = createSecretReference("invalid-provider-id", "Invalid Provider", SECRET_1_NAME);
+ final Optional<Secret> result = secretsManager.getSecret(reference);
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ public void testGetSecretsReturnsAllSecretsProperlyMappedWhenAllAreFound() {
+ final SecretReference reference1 = createSecretReference(PROVIDER_1_ID, PROVIDER_1_NAME, SECRET_1_NAME);
+ final SecretReference reference2 = createSecretReference(PROVIDER_1_ID, PROVIDER_1_NAME, SECRET_2_NAME);
+ final SecretReference reference3 = createSecretReference(PROVIDER_2_ID, PROVIDER_2_NAME, SECRET_3_NAME);
+ final Set<SecretReference> references = Set.of(reference1, reference2, reference3);
+
+ final Map<SecretReference, Secret> results = secretsManager.getSecrets(references);
+ assertEquals(3, results.size());
+
+ final Secret secret1 = results.get(reference1);
+ assertNotNull(secret1);
+ assertEquals(SECRET_1_NAME, secret1.getName());
+ assertEquals(SECRET_1_VALUE, secret1.getValue());
+
+ final Secret secret2 = results.get(reference2);
+ assertNotNull(secret2);
+ assertEquals(SECRET_2_NAME, secret2.getName());
+ assertEquals(SECRET_2_VALUE, secret2.getValue());
+
+ final Secret secret3 = results.get(reference3);
+ assertNotNull(secret3);
+ assertEquals(SECRET_3_NAME, secret3.getName());
+ assertEquals(SECRET_3_VALUE, secret3.getValue());
+ }
+
+ @Test
+ public void testGetSecretsReturnsNullValueForSecretWithInvalidName() {
+ final SecretReference validReference = createSecretReference(PROVIDER_1_ID, PROVIDER_1_NAME, SECRET_1_NAME);
+ final SecretReference invalidReference = createSecretReference(PROVIDER_1_ID, PROVIDER_1_NAME, "non-existent-secret");
+ final Set<SecretReference> references = Set.of(validReference, invalidReference);
+
+ final Map<SecretReference, Secret> results = secretsManager.getSecrets(references);
+ assertEquals(2, results.size());
+
+ final Secret validSecret = results.get(validReference);
+ assertNotNull(validSecret);
+ assertEquals(SECRET_1_NAME, validSecret.getName());
+
+ assertTrue(results.containsKey(invalidReference));
+ assertNull(results.get(invalidReference));
+ }
+
+ @Test
+ public void testGetSecretsReturnsNullValueForSecretWithInvalidProvider() {
+ final SecretReference validReference = createSecretReference(PROVIDER_1_ID, PROVIDER_1_NAME, SECRET_1_NAME);
+ final SecretReference invalidProviderReference = createSecretReference("invalid-id", "Invalid Provider", SECRET_1_NAME);
+ final Set<SecretReference> references = Set.of(validReference, invalidProviderReference);
+
+ final Map<SecretReference, Secret> results = secretsManager.getSecrets(references);
+ assertEquals(2, results.size());
+
+ final Secret validSecret = results.get(validReference);
+ assertNotNull(validSecret);
+
+ assertTrue(results.containsKey(invalidProviderReference));
+ assertNull(results.get(invalidProviderReference));
+ }
+
+ @Test
+ public void testGetSecretsFromMultipleProvidersWithMixedResults() {
+ final SecretReference provider1Secret1 = createSecretReference(PROVIDER_1_ID, null, SECRET_1_NAME);
+ final SecretReference provider1Invalid = createSecretReference(PROVIDER_1_ID, null, "invalid-secret");
+ final SecretReference provider2Secret3 = createSecretReference(PROVIDER_2_ID, null, SECRET_3_NAME);
+ final SecretReference invalidProvider = createSecretReference("invalid-id", null, SECRET_1_NAME);
+
+ final Set<SecretReference> references = Set.of(provider1Secret1, provider1Invalid, provider2Secret3, invalidProvider);
+ final Map<SecretReference, Secret> results = secretsManager.getSecrets(references);
+ assertEquals(4, results.size());
+
+ assertNotNull(results.get(provider1Secret1));
+ assertEquals(SECRET_1_NAME, results.get(provider1Secret1).getName());
+
+ assertTrue(results.containsKey(provider1Invalid));
+ assertNull(results.get(provider1Invalid));
+
+ assertNotNull(results.get(provider2Secret3));
+ assertEquals(SECRET_3_NAME, results.get(provider2Secret3).getName());
+
+ assertTrue(results.containsKey(invalidProvider));
+ assertNull(results.get(invalidProvider));
+ }
+
+ @Test
+ public void testGetSecretProviderSearchesByIdFirst() {
+ final SecretReference referenceWithBothIdAndName = createSecretReference(PROVIDER_1_ID, PROVIDER_2_NAME, SECRET_1_NAME);
+ final Optional<Secret> result = secretsManager.getSecret(referenceWithBothIdAndName);
+ assertTrue(result.isPresent());
+ assertEquals(PROVIDER_1_NAME, result.get().getProviderName());
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorConfiguration.java
index 88c54d2998..4261e2af3d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorConfiguration.java
@@ -32,6 +32,15 @@ public class ConnectorConfiguration {
return stepConfigurations;
}
+ public NamedStepConfiguration getNamedStepConfiguration(final String name) {
+ for (final NamedStepConfiguration stepConfiguration : stepConfigurations) {
+ if (stepConfiguration.stepName().equals(name)) {
+ return stepConfiguration;
+ }
+ }
+ return null;
+ }
+
@Override
public boolean equals(final Object other) {
if (other == null || getClass() != other.getClass()) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
index 3ea48b996a..c951a29f59 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepository.java
@@ -17,6 +17,7 @@
package org.apache.nifi.components.connector;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.VersionedConfigurationStep;
@@ -81,6 +82,8 @@ public interface ConnectorRepository {
void inheritConfiguration(ConnectorNode connector, List<VersionedConfigurationStep> flowConfiguration, Bundle flowContextBundle) throws FlowUpdateException;
+ SecretsManager getSecretsManager();
+
/**
* Creates a new ConnectorStateTransition instance for managing the lifecycle state of a connector.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
index 5b0b923ac7..0f0c50c394 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
@@ -17,6 +17,7 @@
package org.apache.nifi.components.connector;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.controller.NodeTypeProvider;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.nar.ExtensionManager;
@@ -27,6 +28,8 @@ public interface ConnectorRepositoryInitializationContext {
ExtensionManager getExtensionManager();
+ SecretsManager getSecretsManager();
+
NodeTypeProvider getNodeTypeProvider();
ConnectorRequestReplicator getRequestReplicator();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java
index ed977c4b1e..8749e48192 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java
@@ -18,6 +18,7 @@
package org.apache.nifi.components.connector;
import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
public interface FrameworkConnectorInitializationContext extends ConnectorInitializationContext {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java
index b19262e27c..030e333a84 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java
@@ -18,6 +18,7 @@
package org.apache.nifi.components.connector;
import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.logging.ComponentLog;
public interface FrameworkConnectorInitializationContextBuilder {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretProvider.java
similarity index 71%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java
copy to nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretProvider.java
index ed977c4b1e..3c0a2f4e5f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretProvider.java
@@ -15,14 +15,20 @@
* limitations under the License.
*/
-package org.apache.nifi.components.connector;
+package org.apache.nifi.components.connector.secrets;
-import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.components.connector.Secret;
-public interface FrameworkConnectorInitializationContext extends ConnectorInitializationContext {
+import java.util.List;
- SecretsManager getSecretsManager();
+public interface SecretProvider {
- AssetManager getAssetManager();
+ String getProviderId();
+
+ String getProviderName();
+
+ List<Secret> getAllSecrets();
+
+ List<Secret> getSecrets(List<String> fullyQualifiedSecretNames);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretsManager.java
similarity index 57%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
copy to nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretsManager.java
index 5b0b923ac7..62365ed940 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretsManager.java
@@ -15,20 +15,26 @@
* limitations under the License.
*/
-package org.apache.nifi.components.connector;
+package org.apache.nifi.components.connector.secrets;
-import org.apache.nifi.controller.NodeTypeProvider;
-import org.apache.nifi.controller.flow.FlowManager;
-import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.components.connector.SecretReference;
-public interface ConnectorRepositoryInitializationContext {
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
- FlowManager getFlowManager();
+public interface SecretsManager {
- ExtensionManager getExtensionManager();
+ void initialize(SecretsManagerInitializationContext initializationContext);
- NodeTypeProvider getNodeTypeProvider();
+ List<Secret> getAllSecrets();
- ConnectorRequestReplicator getRequestReplicator();
+ Set<SecretProvider> getSecretProviders();
+
+ Optional<Secret> getSecret(SecretReference secretReference);
+
+ Map<SecretReference, Secret> getSecrets(Set<SecretReference> secretReferences);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretsManagerInitializationContext.java
similarity index 75%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java
copy to nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretsManagerInitializationContext.java
index ed977c4b1e..4f4cf8e9a8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/secrets/SecretsManagerInitializationContext.java
@@ -15,14 +15,12 @@
* limitations under the License.
*/
-package org.apache.nifi.components.connector;
+package org.apache.nifi.components.connector.secrets;
-import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.controller.flow.FlowManager;
-public interface FrameworkConnectorInitializationContext extends ConnectorInitializationContext {
+public interface SecretsManagerInitializationContext {
- SecretsManager getSecretsManager();
-
- AssetManager getAssetManager();
+ FlowManager getFlowManager();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ParameterProviderNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ParameterProviderNode.java
index 272d00f9ba..e697f78243 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ParameterProviderNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ParameterProviderNode.java
@@ -49,6 +49,19 @@ public interface ParameterProviderNode extends ComponentNode {
*/
void fetchParameters();
+ /**
+ * Fetches all parameter values from the Parameter Provider and returns them. This method does not cache the results for later retrieval.
+ * @return all Parameter Groups with Parameter Names and Values
+ */
+ List<ParameterGroup> fetchParameterValues();
+
+ /**
+ * Fetches parameters that match the provided fully qualified parameter names. This method does not cache the results for later retrieval.
+ * @param fullyQualifiedParameterNames fully qualified names of parameters to fetch
+ * @return Parameter Groups with Parameter Names and Values that match the provided names
+ */
+ List<ParameterGroup> fetchParameterValues(List<String> fullyQualifiedParameterNames);
+
/**
* Find a named Parameter Group cached from previous request to fetch Parameters from the configured Parameter Provider
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
index 6940112ad9..975bc2aafa 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorConfigurationContext.java
@@ -18,6 +18,8 @@
package org.apache.nifi.components.connector;
import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.components.connector.secrets.SecretProvider;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import java.io.IOException;
import java.io.UncheckedIOException;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
index 89730a5bac..c31cad237f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
@@ -19,6 +19,7 @@ package org.apache.nifi.components.connector;
import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.logging.ComponentLog;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index c1772de292..7e8839dedf 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -577,17 +577,18 @@ public class StandardConnectorNode implements ConnectorNode {
}
final ConfigurationStep configurationStep = optionalStep.get();
- final List<ValidationResult> validationResults = getConnector().validateConfigurationStep(configurationStep, configContext, validationContext);
+
+ final List<ValidationResult> validationResults = new ArrayList<>();
+ validatePropertyReferences(configurationStep, configurationOverrides, validationResults);
+
+ if (validationResults.isEmpty()) {
+ final List<ValidationResult> implValidationResults = getConnector().validateConfigurationStep(configurationStep, configContext, validationContext);
+ validationResults.addAll(implValidationResults);
+ }
final List<ConfigVerificationResult> invalidConfigResults = validationResults.stream()
.filter(result -> !result.isValid())
- .map(validationResult -> new ConfigVerificationResult.Builder()
- .verificationStepName("Property Validation - " + validationResult.getSubject())
- .outcome(Outcome.FAILED)
- .subject(validationResult.getSubject())
- .explanation(validationResult.getExplanation())
- .build()
- )
+ .map(this::createConfigVerificationResult)
.toList();
if (invalidConfigResults.isEmpty()) {
@@ -595,15 +596,25 @@ public class StandardConnectorNode implements ConnectorNode {
.verificationStepName("Property Validation")
.outcome(Outcome.SUCCESSFUL)
.build());
+
+ results.addAll(getConnector().verifyConfigurationStep(stepName, resolvedPropertyOverrides, workingFlowContext));
} else {
results.addAll(invalidConfigResults);
}
- results.addAll(getConnector().verifyConfigurationStep(stepName, resolvedPropertyOverrides, workingFlowContext));
return results;
}
}
+ private ConfigVerificationResult createConfigVerificationResult(final ValidationResult validationResult) {
+ return new ConfigVerificationResult.Builder()
+ .verificationStepName("Property Validation - " + validationResult.getSubject())
+ .outcome(validationResult.isValid() ? Outcome.SUCCESSFUL : Outcome.FAILED)
+ .subject(validationResult.getSubject())
+ .explanation(validationResult.getExplanation())
+ .build();
+ }
+
private Map<String, String> resolvePropertyReferences(final StepConfiguration configurationOverrides) {
final Map<String, String> resolvedProperties = new HashMap<>();
@@ -756,18 +767,23 @@ public class StandardConnectorNode implements ConnectorNode {
final ConnectorValidationContext validationContext = createValidationContext(activeFlowContext);
- List<ValidationResult> allResults;
- try {
- allResults = getConnector().validate(activeFlowContext, validationContext);
- } catch (final Exception e) {
- allResults = List.of(new ValidationResult.Builder()
+ final List<ValidationResult> allResults = new ArrayList<>();
+ validatePropertyReferences(allResults);
+
+ if (allResults.isEmpty()) {
+ try {
+ final List<ValidationResult> implValidationResults = getConnector().validate(activeFlowContext, validationContext);
+ allResults.addAll(implValidationResults);
+ } catch (final Exception e) {
+ allResults.add(new ValidationResult.Builder()
.subject("Validation Failure")
.valid(false)
.explanation("Encountered a failure while attempting to perform validation: " + e.getMessage())
.build());
+ }
}
- if (allResults == null) {
+ if (allResults.isEmpty()) {
return new ValidationState(ValidationStatus.VALID, Collections.emptyList());
}
@@ -786,6 +802,79 @@ public class StandardConnectorNode implements ConnectorNode {
}
}
+
+ private void validatePropertyReferences(final List<ValidationResult> allResults) {
+ final List<ConfigurationStep> configurationSteps = getConnector().getConfigurationSteps(activeFlowContext);
+ final ConnectorConfiguration connectorConfiguration = activeFlowContext.getConfigurationContext().toConnectorConfiguration();
+
+ for (final ConfigurationStep step : configurationSteps) {
+ final NamedStepConfiguration namedStepConfig = connectorConfiguration.getNamedStepConfiguration(step.getName());
+ if (namedStepConfig == null) {
+ continue;
+ }
+
+ validatePropertyReferences(step, namedStepConfig.configuration(), allResults);
+ }
+ }
+
+ private void validatePropertyReferences(final ConfigurationStep step, final StepConfiguration stepConfig, final List<ValidationResult> allResults) {
+ for (final ConnectorPropertyGroup propertyGroup : step.getPropertyGroups()) {
+ for (final ConnectorPropertyDescriptor descriptor : propertyGroup.getProperties()) {
+ final PropertyType propertyType = descriptor.getType();
+ final ConnectorValueReference reference = stepConfig.getPropertyValue(descriptor.getName());
+
+ final String subject = step.getName() + " / " + descriptor.getName();
+
+ if (!isReferenceAllowed(reference, propertyType)) {
+ final String providedReferenceType = switch (reference.getValueType()) {
+ case ASSET_REFERENCE -> "<Asset reference>";
+ case SECRET_REFERENCE -> "<Secret reference>";
+ case STRING_LITERAL -> "<Explicit value>";
+ };
+
+ final String expectedReferenceType = propertyType == PropertyType.SECRET ? "a Secret reference" : "an Explicit value";
+
+ allResults.add(new ValidationResult.Builder()
+ .subject(subject)
+ .input(providedReferenceType)
+ .explanation("This property must be configured with " + expectedReferenceType)
+ .build());
+ }
+ }
+ }
+ }
+
+ private boolean isReferenceAllowed(final ConnectorValueReference reference, final PropertyType propertyType) {
+ // If the reference is null or its value is unset, then it is allowed
+ if (reference == null) {
+ return true;
+ }
+
+ switch (reference) {
+ case StringLiteralValue stringLiteralValue -> {
+ if (stringLiteralValue.getValue() == null) {
+ return true;
+ }
+ }
+ case AssetReference assetReference -> {
+ if (assetReference.getAssetIdentifier() == null) {
+ return true;
+ }
+ }
+ case SecretReference secretReference -> {
+ if (secretReference.getSecretName() == null) {
+ return true;
+ }
+ }
+ }
+
+ if (propertyType == PropertyType.SECRET) {
+ return reference.getValueType() == ConnectorValueType.SECRET_REFERENCE;
+ }
+
+ return reference.getValueType() != ConnectorValueType.SECRET_REFERENCE;
+ }
+
private ConnectorValidationContext createValidationContext(final FrameworkFlowContext context) {
final DescribedValueProvider allowableValueProvider = (stepName, propertyName) ->
fetchAllowableValues(stepName, propertyName, context);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
index d899d13681..4a1b8ed8b3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
@@ -17,6 +17,7 @@
package org.apache.nifi.components.connector;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.controller.NodeTypeProvider;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.nar.ExtensionManager;
@@ -24,15 +25,18 @@ import org.apache.nifi.nar.ExtensionManager;
public class StandardConnectorRepoInitializationContext implements ConnectorRepositoryInitializationContext {
private final FlowManager flowManager;
private final ExtensionManager extensionManager;
+ private final SecretsManager secretsManager;
private final NodeTypeProvider nodeTypeProvider;
private final ConnectorRequestReplicator requestReplicator;
public StandardConnectorRepoInitializationContext(final FlowManager flowManager,
final ExtensionManager extensionManager,
+ final SecretsManager secretsManager,
final NodeTypeProvider nodeTypeProvider,
final ConnectorRequestReplicator requestReplicator) {
this.flowManager = flowManager;
this.extensionManager = extensionManager;
+ this.secretsManager = secretsManager;
this.nodeTypeProvider = nodeTypeProvider;
this.requestReplicator = requestReplicator;
}
@@ -47,6 +51,11 @@ public class StandardConnectorRepoInitializationContext implements ConnectorRepo
return extensionManager;
}
+ @Override
+ public SecretsManager getSecretsManager() {
+ return secretsManager;
+ }
+
@Override
public NodeTypeProvider getNodeTypeProvider() {
return nodeTypeProvider;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index 615595d42f..4cb87da4cf 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -18,6 +18,7 @@
package org.apache.nifi.components.connector;
import org.apache.nifi.annotation.lifecycle.OnRemoved;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.VersionedConfigurationStep;
@@ -43,11 +44,13 @@ public class StandardConnectorRepository implements ConnectorRepository {
private volatile ExtensionManager extensionManager;
private volatile ConnectorRequestReplicator requestReplicator;
+ private volatile SecretsManager secretsManager;
@Override
public void initialize(final ConnectorRepositoryInitializationContext context) {
this.extensionManager = context.getExtensionManager();
this.requestReplicator = context.getRequestReplicator();
+ this.secretsManager = context.getSecretsManager();
}
@Override
@@ -173,6 +176,11 @@ public class StandardConnectorRepository implements ConnectorRepository {
}
}
+ @Override
+ public SecretsManager getSecretsManager() {
+ return secretsManager;
+ }
+
@Override
public ConnectorStateTransition createStateTransition(final String type, final String id) {
final String componentDescription = "StandardConnectorNode[id=" + id + ", type=" + type + "]";
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index f5da41580e..707bdb5266 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -42,7 +42,6 @@ import org.apache.nifi.components.connector.FrameworkConnectorInitializationCont
import org.apache.nifi.components.connector.FrameworkFlowContext;
import org.apache.nifi.components.connector.GhostConnector;
import org.apache.nifi.components.connector.MutableConnectorConfigurationContext;
-import org.apache.nifi.components.connector.SecretsManager;
import org.apache.nifi.components.connector.StandardConnectorNode;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
@@ -543,7 +542,7 @@ public class ExtensionBuilder {
for (final ConnectorPropertyDescriptor descriptor : propertyGroup.getProperties()) {
final String name = descriptor.getName();
final String defaultValue = descriptor.getDefaultValue();
- defaultValues.put(name, new StringLiteralValue(defaultValue));
+ defaultValues.put(name, defaultValue == null ? null : new StringLiteralValue(defaultValue));
}
}
@@ -554,13 +553,12 @@ public class ExtensionBuilder {
private FrameworkConnectorInitializationContext createConnectorInitializationContext(final ProcessGroup managedProcessGroup, final ComponentLog componentLog) {
final String name = type.contains(".") ? StringUtils.substringAfterLast(type, ".") : type;
- final SecretsManager secretsManager = null;
return connectorInitializationContextBuilder
.identifier(identifier)
.name(name)
.componentLog(componentLog)
- .secretsManager(secretsManager)
+ .secretsManager(flowController.getConnectorRepository().getSecretsManager())
.assetManager(flowController.getAssetManager())
.build();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index d119cd23ac..876ec6a70c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -53,6 +53,10 @@ import org.apache.nifi.components.connector.ConnectorRepositoryInitializationCon
import org.apache.nifi.components.connector.ConnectorRequestReplicator;
import org.apache.nifi.components.connector.StandardConnectorRepoInitializationContext;
import org.apache.nifi.components.connector.StandardConnectorRepository;
+import org.apache.nifi.components.connector.secrets.ParameterProviderSecretsManager;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
+import org.apache.nifi.components.connector.secrets.SecretsManagerInitializationContext;
+import org.apache.nifi.components.connector.secrets.StandardSecretsManagerInitializationContext;
import org.apache.nifi.components.monitor.LongRunningTaskMonitor;
import org.apache.nifi.components.state.StateManagerProvider;
import org.apache.nifi.components.state.StateProvider;
@@ -269,6 +273,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
public static final String DEFAULT_SWAP_MANAGER_IMPLEMENTATION = "org.apache.nifi.controller.FileSystemSwapManager";
public static final String DEFAULT_ASSET_MANAGER_IMPLEMENTATION = StandardAssetManager.class.getName();
public static final String DEFAULT_CONNECTOR_REPOSITORY_IMPLEMENTATION = StandardConnectorRepository.class.getName();
+ public static final String DEFAULT_SECRETS_MANAGER_IMPLEMENTATION = ParameterProviderSecretsManager.class.getName();
public static final String GRACEFUL_SHUTDOWN_PERIOD = "nifi.flowcontroller.graceful.shutdown.seconds";
public static final long DEFAULT_GRACEFUL_SHUTDOWN_SECONDS = 10;
@@ -603,7 +608,8 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
controllerServiceResolver = new StandardControllerServiceResolver(authorizer, flowManager, new VersionedComponentFlowMapper(extensionManager),
controllerServiceProvider, new StandardControllerServiceApiLookup(extensionManager));
- connectorRepository = createConnectorRepository(nifiProperties, extensionManager, flowManager, this, connectorRequestReplicator);
+ final SecretsManager secretsManager = createSecretsManager(nifiProperties, extensionManager, flowManager);
+ connectorRepository = createConnectorRepository(nifiProperties, extensionManager, flowManager, secretsManager, this, connectorRequestReplicator);
final PythonBridge rawPythonBridge = createPythonBridge(nifiProperties, controllerServiceProvider);
final ClassLoader pythonBridgeClassLoader = rawPythonBridge.getClass().getClassLoader();
@@ -876,7 +882,7 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
}
private static ConnectorRepository createConnectorRepository(final NiFiProperties properties, final ExtensionDiscoveringManager extensionManager, final FlowManager flowManager,
- final NodeTypeProvider nodeTypeProvider, final ConnectorRequestReplicator requestReplicator) {
+ final SecretsManager secretsManager, final NodeTypeProvider nodeTypeProvider, final ConnectorRequestReplicator requestReplicator) {
final String implementationClassName = properties.getProperty(NiFiProperties.CONNECTOR_REPOSITORY_IMPLEMENTATION, DEFAULT_CONNECTOR_REPOSITORY_IMPLEMENTATION);
@@ -884,12 +890,14 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
// Discover implementations of Connector Repository. This is not done at startup because the ConnectorRepository class is not
// provided in the list of standard extension points. This is due to the fact that ConnectorRepository lives in the nifi-framework-core-api, and
// does not make sense to refactor it into some other module due to its dependencies, simply to allow it to be discovered at startup.
- extensionManager.discoverExtensions(extensionManager.getAllBundles(), Set.of(ConnectorRepository.class), true);
+ final Set<Class<?>> additionalExtensionTypes = Set.of(ConnectorRepository.class, SecretsManager.class);
+ extensionManager.discoverExtensions(extensionManager.getAllBundles(), additionalExtensionTypes, true);
final ConnectorRepository created = NarThreadContextClassLoader.createInstance(extensionManager, implementationClassName, ConnectorRepository.class, properties);
final ConnectorRepositoryInitializationContext initializationContext = new StandardConnectorRepoInitializationContext(
flowManager,
extensionManager,
+ secretsManager,
nodeTypeProvider,
requestReplicator
);
@@ -901,6 +909,35 @@ public class FlowController implements ReportingTaskProvider, FlowAnalysisRulePr
}
}
+ LOG.info("Created Connector Repository of type {}", created.getClass().getSimpleName());
+
+ return created;
+ } catch (final Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static SecretsManager createSecretsManager(final NiFiProperties properties, final ExtensionDiscoveringManager extensionManager, final FlowManager flowManager) {
+ final String implementationClassName = properties.getProperty(NiFiProperties.SECRETS_MANAGER_IMPLEMENTATION, DEFAULT_SECRETS_MANAGER_IMPLEMENTATION);
+
+ try {
+ // Discover implementations of Secrets Manager. This is not done at startup because the SecretsManager class is not
+ // provided in the list of standard extension points. This is due to the fact that SecretsManager lives in the nifi-framework-core-api, and
+ // does not make sense to refactor it into some other module due to its dependencies, simply to allow it to be discovered at startup.
+ extensionManager.discoverExtensions(extensionManager.getAllBundles(), Set.of(SecretsManager.class), true);
+ final SecretsManager created = NarThreadContextClassLoader.createInstance(extensionManager, implementationClassName, SecretsManager.class, properties);
+
+ final SecretsManagerInitializationContext initializationContext = new StandardSecretsManagerInitializationContext(flowManager);
+
+ synchronized (created) {
+ // Ensure that any NAR dependencies are available when we initialize the ConnectorRepository
+ try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, created.getClass(), "secrets-manager")) {
+ created.initialize(initializationContext);
+ }
+ }
+
+ LOG.info("Created Secrets Manager of type {}", created.getClass().getSimpleName());
+
return created;
} catch (final Exception e) {
throw new RuntimeException(e);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index d4e5fd6e87..804d0f732e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -764,7 +764,9 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
final ConnectorRepository connectorRepository = flowController.getConnectorRepository();
final ConnectorStateTransition stateTransition = connectorRepository.createStateTransition(type, id);
- final StandardConnectorConfigurationContext activeConfigurationContext = new StandardConnectorConfigurationContext(flowController.getAssetManager(), null);
+ final StandardConnectorConfigurationContext activeConfigurationContext = new StandardConnectorConfigurationContext(
+ flowController.getAssetManager(), flowController.getConnectorRepository().getSecretsManager());
+
final ProcessGroupFactory processGroupFactory = groupId -> createProcessGroup(groupId, false);
final FlowContextFactory flowContextFactory = new FlowControllerFlowContextFactory(flowController, managedRootGroup, activeConfigurationContext, processGroupFactory);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorConfigurationContext.java
index f3da3a8e50..9c8b6b6e08 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorConfigurationContext.java
@@ -18,6 +18,7 @@
package org.apache.nifi.components.connector;
import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index 40f2323b9b..5147fb0d04 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -23,6 +23,7 @@ import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.components.connector.components.FlowContextType;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.flow.Bundle;
(nifi) 22/40: NIFI-15429: Adding an optional query parameter for specifying which p… (#10732)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit f931e1497ec79287f887b848f8a64d943ea271d0
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Wed Jan 7 11:22:25 2026 -0500
NIFI-15429: Adding an optional query parameter for specifying which p… (#10732)
* NIFI-15429: Adding an optional query parameter for specifying which process group should be returned within the Connectors flow.
* NIFI-15429: Including managed process group id in the ConnectorDTO.
* NIFI-15429: Addressing review feedback.
* NIFI-15429: Updating path for fetching a Connector flow to require the desired Process Group ID.
* NIFI-15429: Fixing failing unit test.
---
.../org/apache/nifi/web/api/dto/ConnectorDTO.java | 10 ++++++
.../org/apache/nifi/web/NiFiServiceFacade.java | 2 +-
.../apache/nifi/web/StandardNiFiServiceFacade.java | 10 ++++--
.../org/apache/nifi/web/api/ConnectorResource.java | 32 +++++++++++-------
.../org/apache/nifi/web/api/dto/DtoFactory.java | 5 ++-
.../apache/nifi/web/api/TestConnectorResource.java | 39 ++++++++++++++++++++++
.../nifi/web/controller/ControllerFacadeTest.java | 3 ++
7 files changed, 84 insertions(+), 17 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
index 9aed99c62b..96849a58a0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
@@ -30,6 +30,7 @@ public class ConnectorDTO extends ComponentDTO {
private String type;
private BundleDTO bundle;
private String state; // RUNNING, STOPPED, DISABLED
+ private String managedProcessGroupId;
private ConnectorConfigurationDTO activeConfiguration;
private ConnectorConfigurationDTO workingConfiguration;
@@ -76,6 +77,15 @@ public class ConnectorDTO extends ComponentDTO {
this.state = state;
}
+ @Schema(description = "The identifier of the root Process Group managed by this Connector.")
+ public String getManagedProcessGroupId() {
+ return managedProcessGroupId;
+ }
+
+ public void setManagedProcessGroupId(final String managedProcessGroupId) {
+ this.managedProcessGroupId = managedProcessGroupId;
+ }
+
@Schema(description = "The active configuration of the Connector.")
public ConnectorConfigurationDTO getActiveConfiguration() {
return activeConfiguration;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 1928b17397..09761a5076 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -220,7 +220,7 @@ public interface NiFiServiceFacade {
ConnectorEntity discardConnectorUpdate(Revision revision, String connectorId);
- ProcessGroupFlowEntity getConnectorFlow(String id, boolean uiOnly);
+ ProcessGroupFlowEntity getConnectorFlow(String connectorId, String processGroupId, boolean uiOnly);
ProcessGroupStatusEntity getConnectorProcessGroupStatus(String id, Boolean recursive);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 3b2b7d1165..c540467b38 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -3690,10 +3690,14 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
@Override
- public ProcessGroupFlowEntity getConnectorFlow(final String id, final boolean uiOnly) {
- final ConnectorNode connectorNode = connectorDAO.getConnector(id);
+ public ProcessGroupFlowEntity getConnectorFlow(final String connectorId, final String processGroupId, final boolean uiOnly) {
+ final ConnectorNode connectorNode = connectorDAO.getConnector(connectorId);
final ProcessGroup managedProcessGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
- return createProcessGroupFlowEntity(managedProcessGroup, uiOnly);
+ final ProcessGroup targetProcessGroup = managedProcessGroup.findProcessGroup(processGroupId);
+ if (targetProcessGroup == null) {
+ throw new ResourceNotFoundException("Process Group with ID " + processGroupId + " was not found within Connector " + connectorId);
+ }
+ return createProcessGroupFlowEntity(targetProcessGroup, uiOnly);
}
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
index 0112d7e61c..c2f7b4cddf 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
@@ -1348,18 +1348,19 @@ public class ConnectorResource extends ApplicationResource {
}
/**
- * Retrieves the flow for the process group managed by the specified connector.
+ * Retrieves the flow for a process group within the specified connector.
*
- * @param id The id of the connector
+ * @param connectorId The id of the connector
+ * @param processGroupId The process group id within the connector's hierarchy
* @param uiOnly Whether to return only UI-specific fields
* @return A processGroupFlowEntity
*/
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
- @Path("/{id}/flow")
+ @Path("/{connectorId}/flow/process-groups/{processGroupId}")
@Operation(
- summary = "Gets the flow for the process group managed by a connector",
+ summary = "Gets the flow for a process group within a connector",
responses = {
@ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ProcessGroupFlowEntity.class))),
@ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@@ -1371,17 +1372,24 @@ public class ConnectorResource extends ApplicationResource {
security = {
@SecurityRequirement(name = "Read - /connectors/{uuid}")
},
- description = "Returns the flow for the process group managed by the specified connector. If the uiOnly query parameter is " +
- "provided with a value of true, the returned entity may only contain fields that are necessary for rendering the NiFi User " +
- "Interface. As such, the selected fields may change at any time, even during incremental releases, without warning. " +
- "As a result, this parameter should not be provided by any client other than the UI."
+ description = "Returns the flow for the specified process group within the connector's hierarchy. The processGroupId can be " +
+ "obtained from the managedProcessGroupId field of the ConnectorDTO for the root process group, or from child process " +
+ "groups within the flow. If the uiOnly query parameter is provided with a value of true, the returned entity may only " +
+ "contain fields that are necessary for rendering the NiFi User Interface. As such, the selected fields may change at " +
+ "any time, even during incremental releases, without warning. As a result, this parameter should not be provided by " +
+ "any client other than the UI."
)
public Response getFlow(
@Parameter(
description = "The connector id.",
required = true
)
- @PathParam("id") final String id,
+ @PathParam("connectorId") final String connectorId,
+ @Parameter(
+ description = "The process group id.",
+ required = true
+ )
+ @PathParam("processGroupId") final String processGroupId,
@Parameter(
description = "Whether to return only UI-specific fields"
)
@@ -1393,12 +1401,12 @@ public class ConnectorResource extends ApplicationResource {
// authorize access to the connector
serviceFacade.authorizeAccess(lookup -> {
- final Authorizable connector = lookup.getConnector(id);
+ final Authorizable connector = lookup.getConnector(connectorId);
connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
- // get the flow for the connector's managed process group
- final ProcessGroupFlowEntity entity = serviceFacade.getConnectorFlow(id, uiOnly);
+ // get the flow for the specified process group within the connector's hierarchy
+ final ProcessGroupFlowEntity entity = serviceFacade.getConnectorFlow(connectorId, processGroupId, uiOnly);
flowResource.populateRemainingFlowContent(entity.getProcessGroupFlow());
return generateOkResponse(entity).build();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 72686f650c..1fa240ced1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -5262,7 +5262,10 @@ public final class DtoFactory {
dto.setBundle(createBundleDto(connector.getBundleCoordinate()));
dto.setState(connector.getCurrentState().name());
- dto.setActiveConfiguration(createConnectorConfigurationDtoFromFlowContext(connector, connector.getActiveFlowContext()));
+
+ final FrameworkFlowContext activeFlowContext = connector.getActiveFlowContext();
+ dto.setManagedProcessGroupId(activeFlowContext.getManagedProcessGroup().getIdentifier());
+ dto.setActiveConfiguration(createConnectorConfigurationDtoFromFlowContext(connector, activeFlowContext));
dto.setWorkingConfiguration(createConnectorConfigurationDtoFromFlowContext(connector, connector.getWorkingFlowContext()));
return dto;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
index df21a6935e..5c22f82fd1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
@@ -32,9 +32,11 @@ import org.apache.nifi.web.api.dto.AllowableValueDTO;
import org.apache.nifi.web.api.dto.ConnectorDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.entity.AllowableValueEntity;
+import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.nifi.web.api.request.LongParameter;
@@ -86,12 +88,16 @@ public class TestConnectorResource {
@Mock
private UriBuilder uriBuilder;
+ @Mock
+ private FlowResource flowResource;
+
private static final String CONNECTOR_ID = "test-connector-id";
private static final String CONNECTOR_NAME = "Test Connector";
private static final String CONNECTOR_TYPE = "TestConnectorType";
private static final String CONFIGURATION_STEP_NAME = "test-step";
private static final String PROPERTY_GROUP_NAME = "test-group";
private static final String PROPERTY_NAME = "test-property";
+ private static final String PROCESS_GROUP_ID = "test-process-group-id";
@BeforeEach
public void setUp() throws Exception {
@@ -109,6 +115,7 @@ public class TestConnectorResource {
lenient().when(uriBuilder.build()).thenReturn(new URI("http://localhost:8080/nifi-api/connectors/" + CONNECTOR_ID));
connectorResource.setServiceFacade(serviceFacade);
+ connectorResource.setFlowResource(flowResource);
connectorResource.httpServletRequest = httpServletRequest;
connectorResource.properties = properties;
connectorResource.uriInfo = uriInfo;
@@ -353,6 +360,30 @@ public class TestConnectorResource {
verify(serviceFacade, never()).getSecrets();
}
+ @Test
+ public void testGetFlow() {
+ final ProcessGroupFlowEntity responseEntity = createProcessGroupFlowEntity();
+ when(serviceFacade.getConnectorFlow(CONNECTOR_ID, PROCESS_GROUP_ID, false)).thenReturn(responseEntity);
+
+ try (Response response = connectorResource.getFlow(CONNECTOR_ID, PROCESS_GROUP_ID, false)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(responseEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade).getConnectorFlow(CONNECTOR_ID, PROCESS_GROUP_ID, false);
+ }
+
+ @Test
+ public void testGetFlowNotAuthorized() {
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () -> connectorResource.getFlow(CONNECTOR_ID, PROCESS_GROUP_ID, false));
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade, never()).getConnectorFlow(anyString(), anyString(), eq(false));
+ }
+
private ConnectorEntity createConnectorEntity() {
final ConnectorEntity entity = new ConnectorEntity();
@@ -411,4 +442,12 @@ public class TestConnectorResource {
return entity;
}
+
+ private ProcessGroupFlowEntity createProcessGroupFlowEntity() {
+ final ProcessGroupFlowEntity entity = new ProcessGroupFlowEntity();
+ final ProcessGroupFlowDTO flowDTO = new ProcessGroupFlowDTO();
+ flowDTO.setId("root-process-group-id");
+ entity.setProcessGroupFlow(flowDTO);
+ return entity;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java
index 6374029cac..5dad329baf 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java
@@ -132,6 +132,9 @@ public class ControllerFacadeTest {
@Test
public void testGetConnectorDefinitionReturnsDefinitionWhenFound() {
final ConnectorDefinition expectedDefinition = new ConnectorDefinition();
+ expectedDefinition.setGroup(TEST_GROUP);
+ expectedDefinition.setArtifact(TEST_ARTIFACT);
+ expectedDefinition.setVersion(TEST_VERSION);
expectedDefinition.setType(TEST_CONNECTOR_TYPE);
when(runtimeManifestService.getManifestForBundle(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION)).thenReturn(runtimeManifest);
(nifi) 21/40: NIFI-15353: Adding support for rendering Connector Documentation. (#10683)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 39fe9e1dae8f9f8f513dcb281c7c1509ab8807ec
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Tue Dec 23 11:50:53 2025 -0500
NIFI-15353: Adding support for rendering Connector Documentation. (#10683)
* NIFI-15353: Adding support for rendering Connector Documentation.
* NIFI-15353: Addressing review feedback.
* NIFI-15353: Using mergeMap instead of switchMap to allow for concurrent doc loading.
This closes #10683
---
.../protocol/component/api/ComponentManifest.java | 10 +
.../protocol/component/api/ConfigurationStep.java | 81 ++++
.../component/api/ConfigurationStepDependency.java | 61 +++
.../component/api/ConnectorDefinition.java | 31 +-
.../component/api/ConnectorPropertyDependency.java | 51 +++
.../component/api/ConnectorPropertyDescriptor.java | 111 ++++++
.../component/api/ConnectorPropertyGroup.java | 61 +++
.../component/api/ConnectorPropertyType.java | 25 +-
.../steps}/Kafka_Connection.md | 0
.../steps}/Kafka_Topics.md | 0
.../steps}/S3_Configuration.md | 0
.../manifest/StandardRuntimeManifestService.java | 4 +-
.../org/apache/nifi/web/NiFiServiceFacade.java | 12 +
.../apache/nifi/web/StandardNiFiServiceFacade.java | 10 +
.../java/org/apache/nifi/web/api/FlowResource.java | 57 ++-
.../nifi/web/controller/ControllerFacade.java | 24 ++
.../nifi/web/controller/ControllerFacadeTest.java | 83 +++++
.../feature/documentation-routing.module.ts | 5 +
.../feature/documentation.component.html | 13 +
.../feature/documentation.component.ts | 8 +
.../documentation/feature/documentation.module.ts | 2 +
.../documentation/service/documentation.service.ts | 17 +
.../connector-definition.actions.ts | 56 +++
.../connector-definition.effects.ts | 79 ++++
.../connector-definition.reducer.ts | 92 +++++
.../connector-definition.selectors.ts | 31 +-
.../state/connector-definition/index.ts | 73 ++++
.../src/app/pages/documentation/state/index.ts | 4 +
.../connector-property-definition.component.html | 95 +++++
.../connector-property-definition.component.scss | 22 +-
...connector-property-definition.component.spec.ts | 176 +++++++++
.../connector-property-definition.component.ts | 88 +++++
.../connector-definition.component.html | 209 +++++++++++
.../connector-definition.component.scss | 22 +-
.../connector-definition.component.spec.ts | 415 +++++++++++++++++++++
.../connector-definition.component.ts | 204 ++++++++++
.../flow-analysis-rule-table.component.spec.ts | 4 +
...anagement-controller-services.component.spec.ts | 4 +
.../parameter-providers-table.component.spec.ts | 4 +
.../reporting-task-table.component.spec.ts | 4 +
.../user-table/user-table.component.spec.ts | 4 +
.../src/app/service/extension-types.service.ts | 4 +
.../extension-types/extension-types.effects.ts | 9 +-
.../extension-types/extension-types.reducer.ts | 3 +
.../extension-types/extension-types.selectors.ts | 8 +-
.../nifi/src/app/state/extension-types/index.ts | 2 +
.../main/frontend/libs/shared/src/types/index.ts | 1 +
.../nifi/extension/manifest/ConfigurationStep.java | 80 ++++
.../manifest/ConfigurationStepDependency.java | 67 ++++
.../nifi/extension/manifest/ConnectorProperty.java | 120 ++++++
.../manifest/ConnectorPropertyDependency.java | 57 +++
.../extension/manifest/ConnectorPropertyGroup.java | 67 ++++
...tensionType.java => ConnectorPropertyType.java} | 23 +-
.../apache/nifi/extension/manifest/Extension.java | 14 +
.../nifi/extension/manifest/ExtensionType.java | 4 +-
.../runtime/manifest/ComponentManifestBuilder.java | 7 +
.../impl/StandardComponentManifestBuilder.java | 12 +
.../impl/StandardRuntimeManifestBuilder.java | 126 +++++++
...tandardRuntimeManifestBuilderConnectorTest.java | 349 +++++++++++++++++
pom.xml | 2 +-
60 files changed, 3112 insertions(+), 95 deletions(-)
diff --git a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ComponentManifest.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ComponentManifest.java
index 1bb5fb07d9..38bd55573e 100644
--- a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ComponentManifest.java
+++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ComponentManifest.java
@@ -33,6 +33,7 @@ public class ComponentManifest implements Serializable {
private List<ParameterProviderDefinition> parameterProviders;
private List<FlowRegistryClientDefinition> flowRegistryClients;
private List<FlowAnalysisRuleDefinition> flowAnalysisRules;
+ private List<ConnectorDefinition> connectors;
@Schema(description = "Public interfaces defined in this bundle")
public List<DefinedType> getApis() {
@@ -97,4 +98,13 @@ public class ComponentManifest implements Serializable {
this.flowRegistryClients = flowRegistryClients;
}
+ @Schema(description = "Connectors provided in this bundle")
+ public List<ConnectorDefinition> getConnectors() {
+ return (connectors != null ? Collections.unmodifiableList(connectors) : null);
+ }
+
+ public void setConnectors(List<ConnectorDefinition> connectors) {
+ this.connectors = connectors;
+ }
+
}
diff --git a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConfigurationStep.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConfigurationStep.java
new file mode 100644
index 0000000000..88d76b69af
--- /dev/null
+++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConfigurationStep.java
@@ -0,0 +1,81 @@
+/*
+ * 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.c2.protocol.component.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Represents a configuration step for a Connector.
+ */
+public class ConfigurationStep implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private String description;
+ private boolean documented;
+ private List<ConfigurationStepDependency> stepDependencies;
+ private List<ConnectorPropertyGroup> propertyGroups;
+
+ @Schema(description = "The name of the configuration step")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "The description of the configuration step")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Schema(description = "Whether this configuration step has additional documentation")
+ public boolean isDocumented() {
+ return documented;
+ }
+
+ public void setDocumented(boolean documented) {
+ this.documented = documented;
+ }
+
+ @Schema(description = "The dependencies that this step has on other steps")
+ public List<ConfigurationStepDependency> getStepDependencies() {
+ return stepDependencies;
+ }
+
+ public void setStepDependencies(List<ConfigurationStepDependency> stepDependencies) {
+ this.stepDependencies = stepDependencies;
+ }
+
+ @Schema(description = "The property groups in this configuration step")
+ public List<ConnectorPropertyGroup> getPropertyGroups() {
+ return propertyGroups;
+ }
+
+ public void setPropertyGroups(List<ConnectorPropertyGroup> propertyGroups) {
+ this.propertyGroups = propertyGroups;
+ }
+}
+
diff --git a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConfigurationStepDependency.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConfigurationStepDependency.java
new file mode 100644
index 0000000000..1f1bc2a24d
--- /dev/null
+++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConfigurationStepDependency.java
@@ -0,0 +1,61 @@
+/*
+ * 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.c2.protocol.component.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Represents a dependency that a configuration step has on another step's property.
+ */
+public class ConfigurationStepDependency implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String stepName;
+ private String propertyName;
+ private List<String> dependentValues;
+
+ @Schema(description = "The name of the step that this step depends on")
+ public String getStepName() {
+ return stepName;
+ }
+
+ public void setStepName(String stepName) {
+ this.stepName = stepName;
+ }
+
+ @Schema(description = "The name of the property within the step that this step depends on")
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ public void setPropertyName(String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ @Schema(description = "The values of the dependent property that enable this step")
+ public List<String> getDependentValues() {
+ return dependentValues;
+ }
+
+ public void setDependentValues(List<String> dependentValues) {
+ this.dependentValues = dependentValues;
+ }
+}
+
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorDefinition.java
similarity index 52%
copy from nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
copy to c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorDefinition.java
index 0413e260ed..9ac1c85eea 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
+++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorDefinition.java
@@ -14,23 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.extension.manifest;
+package org.apache.nifi.c2.protocol.component.api;
-/**
- * Possible types of extensions.
- */
-public enum ExtensionType {
-
- PROCESSOR,
-
- CONTROLLER_SERVICE,
+import io.swagger.v3.oas.annotations.media.Schema;
- REPORTING_TASK,
+import java.util.Collections;
+import java.util.List;
- FLOW_ANALYSIS_RULE,
+/**
+ * Definition of a Connector component.
+ */
+public class ConnectorDefinition extends ExtensionComponent {
+ private static final long serialVersionUID = 1L;
- PARAMETER_PROVIDER,
+ private List<ConfigurationStep> configurationSteps;
- FLOW_REGISTRY_CLIENT;
+ @Schema(description = "The configuration steps for this connector")
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return (configurationSteps != null ? Collections.unmodifiableList(configurationSteps) : null);
+ }
+ public void setConfigurationSteps(List<ConfigurationStep> configurationSteps) {
+ this.configurationSteps = configurationSteps;
+ }
}
+
diff --git a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyDependency.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyDependency.java
new file mode 100644
index 0000000000..b01b4143e0
--- /dev/null
+++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyDependency.java
@@ -0,0 +1,51 @@
+/*
+ * 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.c2.protocol.component.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Represents a dependency that a connector property has on another property.
+ */
+public class ConnectorPropertyDependency implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String propertyName;
+ private List<String> dependentValues;
+
+ @Schema(description = "The name of the property that this property depends on")
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ public void setPropertyName(String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ @Schema(description = "The values of the dependent property that enable this property")
+ public List<String> getDependentValues() {
+ return dependentValues;
+ }
+
+ public void setDependentValues(List<String> dependentValues) {
+ this.dependentValues = dependentValues;
+ }
+}
+
diff --git a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyDescriptor.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyDescriptor.java
new file mode 100644
index 0000000000..fc9b4519e4
--- /dev/null
+++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyDescriptor.java
@@ -0,0 +1,111 @@
+/*
+ * 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.c2.protocol.component.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Represents a property descriptor for a Connector.
+ */
+public class ConnectorPropertyDescriptor implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private String description;
+ private String defaultValue;
+ private boolean required;
+ private ConnectorPropertyType propertyType;
+ private boolean allowableValuesFetchable;
+ private List<PropertyAllowableValue> allowableValues;
+ private List<ConnectorPropertyDependency> dependencies;
+
+ @Schema(description = "The name of the property")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "The description of the property")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Schema(description = "The default value of the property")
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public void setDefaultValue(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Schema(description = "Whether or not the property is required")
+ public boolean isRequired() {
+ return required;
+ }
+
+ public void setRequired(boolean required) {
+ this.required = required;
+ }
+
+ @Schema(description = "The type of the property")
+ public ConnectorPropertyType getPropertyType() {
+ return propertyType;
+ }
+
+ public void setPropertyType(ConnectorPropertyType propertyType) {
+ this.propertyType = propertyType;
+ }
+
+ @Schema(description = "Whether or not the allowable values can be fetched dynamically")
+ public boolean isAllowableValuesFetchable() {
+ return allowableValuesFetchable;
+ }
+
+ public void setAllowableValuesFetchable(boolean allowableValuesFetchable) {
+ this.allowableValuesFetchable = allowableValuesFetchable;
+ }
+
+ @Schema(description = "The allowable values for this property")
+ public List<PropertyAllowableValue> getAllowableValues() {
+ return allowableValues;
+ }
+
+ public void setAllowableValues(List<PropertyAllowableValue> allowableValues) {
+ this.allowableValues = allowableValues;
+ }
+
+ @Schema(description = "The properties that this property depends on")
+ public List<ConnectorPropertyDependency> getDependencies() {
+ return dependencies;
+ }
+
+ public void setDependencies(List<ConnectorPropertyDependency> dependencies) {
+ this.dependencies = dependencies;
+ }
+}
+
diff --git a/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyGroup.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyGroup.java
new file mode 100644
index 0000000000..8eaab4ec97
--- /dev/null
+++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyGroup.java
@@ -0,0 +1,61 @@
+/*
+ * 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.c2.protocol.component.api;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Represents a group of properties within a configuration step.
+ */
+public class ConnectorPropertyGroup implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private String description;
+ private List<ConnectorPropertyDescriptor> properties;
+
+ @Schema(description = "The name of the property group")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "The description of the property group")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Schema(description = "The properties in this group")
+ public List<ConnectorPropertyDescriptor> getProperties() {
+ return properties;
+ }
+
+ public void setProperties(List<ConnectorPropertyDescriptor> properties) {
+ this.properties = properties;
+ }
+}
+
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyType.java
similarity index 76%
copy from nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
copy to c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyType.java
index 0413e260ed..906673ec9d 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
+++ b/c2/c2-protocol/c2-protocol-component-api/src/main/java/org/apache/nifi/c2/protocol/component/api/ConnectorPropertyType.java
@@ -14,23 +14,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.extension.manifest;
+package org.apache.nifi.c2.protocol.component.api;
/**
- * Possible types of extensions.
+ * Possible types for connector properties.
*/
-public enum ExtensionType {
+public enum ConnectorPropertyType {
- PROCESSOR,
+ STRING,
- CONTROLLER_SERVICE,
+ INTEGER,
- REPORTING_TASK,
+ BOOLEAN,
- FLOW_ANALYSIS_RULE,
+ FLOAT,
- PARAMETER_PROVIDER,
+ DOUBLE,
- FLOW_REGISTRY_CLIENT;
+ STRING_LIST,
+
+ SECRET,
+
+ ASSET,
+
+ ASSET_LIST;
}
+
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Connection.md b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/docs/org.apache.nifi.connectors.kafkas3.KafkaToS3/steps/Kafka_Connection.md
similarity index 100%
rename from nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Connection.md
rename to nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/docs/org.apache.nifi.connectors.kafkas3.KafkaToS3/steps/Kafka_Connection.md
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Topics.md b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/docs/org.apache.nifi.connectors.kafkas3.KafkaToS3/steps/Kafka_Topics.md
similarity index 100%
rename from nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/Kafka_Topics.md
rename to nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/docs/org.apache.nifi.connectors.kafkas3.KafkaToS3/steps/Kafka_Topics.md
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/S3_Configuration.md b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/docs/org.apache.nifi.connectors.kafkas3.KafkaToS3/steps/S3_Configuration.md
similarity index 100%
rename from nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/src/main/resources/META-INF/docs/step-documentation/org.apache.nifi.connectors.kafkas3.KafkaToS3/S3_Configuration.md
rename to nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/docs/org.apache.nifi.connectors.kafkas3.KafkaToS3/steps/S3_Configuration.md
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java
index 197951ccb0..320f59af99 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/manifest/StandardRuntimeManifestService.java
@@ -416,9 +416,9 @@ public class StandardRuntimeManifestService implements RuntimeManifestService {
private Map<String, File> discoverStepDocumentation(final BundleDetails bundleDetails, final String connectorType) {
final Map<String, File> stepDocsMap = new LinkedHashMap<>();
- final File stepDocsDir = new File(bundleDetails.getWorkingDirectory(), "META-INF/docs/step-documentation/" + connectorType);
+ final File stepDocsDir = new File(bundleDetails.getWorkingDirectory(), "META-INF/docs/steps/" + connectorType);
if (!stepDocsDir.exists()) {
- LOGGER.debug("No step-documentation directory found for [{}] under [{}]", connectorType, bundleDetails.getWorkingDirectory().getAbsolutePath());
+ LOGGER.debug("No steps directory found for [{}] under [{}]", connectorType, bundleDetails.getWorkingDirectory().getAbsolutePath());
return stepDocsMap;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 14472b78bd..1928b17397 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -22,6 +22,7 @@ import org.apache.nifi.authorization.AuthorizeAccess;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowAnalysisRuleDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowRegistryClientDefinition;
@@ -643,6 +644,17 @@ public interface NiFiServiceFacade {
*/
FlowAnalysisRuleDefinition getFlowAnalysisRuleDefinition(String group, String artifact, String version, String type);
+ /**
+ * Return the ConnectorDefinition for the specified Connector.
+ *
+ * @param group The bundle group
+ * @param artifact The bundle artifact
+ * @param version The bundle version
+ * @param type The Connector type
+ * @return The ConnectorDefinition
+ */
+ ConnectorDefinition getConnectorDefinition(String group, String artifact, String version, String type);
+
/**
* Return the additionalDetails for the specified component.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index a1472b8c6d..3b2b7d1165 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -52,6 +52,7 @@ import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowAnalysisRuleDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowRegistryClientDefinition;
@@ -4370,6 +4371,15 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return flowAnalysisRuleDefinition;
}
+ @Override
+ public ConnectorDefinition getConnectorDefinition(final String group, final String artifact, final String version, final String type) {
+ final ConnectorDefinition connectorDefinition = controllerFacade.getConnectorDefinition(group, artifact, version, type);
+ if (connectorDefinition == null) {
+ throw new ResourceNotFoundException("Unable to find definition for [%s:%s:%s:%s]".formatted(group, artifact, version, type));
+ }
+ return connectorDefinition;
+ }
+
@Override
public String getAdditionalDetails(String group, String artifact, String version, String type) {
return controllerFacade.getAdditionalDetails(group, artifact, version, type);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
index ff48508762..80fa3f80d5 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
@@ -54,6 +54,7 @@ import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowAnalysisRuleDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowRegistryClientDefinition;
@@ -2286,6 +2287,60 @@ public class FlowResource extends ApplicationResource {
return generateOkResponse(entity).build();
}
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("connector-definition/{group}/{artifact}/{version}/{type}")
+ @Operation(
+ summary = "Retrieves the Connector Definition for the specified component type.",
+ description = NON_GUARANTEED_ENDPOINT,
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorDefinition.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The connector definition for the coordinates could not be located.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /flow")
+ }
+ )
+ public Response getConnectorDefinition(
+ @Parameter(
+ description = "The bundle group",
+ required = true
+ )
+ @PathParam("group") final String group,
+ @Parameter(
+ description = "The bundle artifact",
+ required = true
+ )
+ @PathParam("artifact") final String artifact,
+ @Parameter(
+ description = "The bundle version",
+ required = true
+ )
+ @PathParam("version") final String version,
+ @Parameter(
+ description = "The connector type",
+ required = true
+ )
+ @PathParam("type") final String type
+ ) throws InterruptedException {
+
+ authorizeFlow();
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // create response entity
+ final ConnectorDefinition entity = serviceFacade.getConnectorDefinition(group, artifact, version, type);
+
+ // generate the response
+ return generateOkResponse(entity).build();
+ }
+
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
@@ -2345,7 +2400,7 @@ public class FlowResource extends ApplicationResource {
@GET
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.APPLICATION_JSON)
- @Path("step-documentation/{group}/{artifact}/{version}/{connectorType}/{stepName}")
+ @Path("steps/{group}/{artifact}/{version}/{connectorType}/{stepName}")
@Operation(
summary = "Retrieves the step documentation for the specified Connector configuration step.",
description = NON_GUARANTEED_ENDPOINT,
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 95d6f7f9e8..dd1456288f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -33,6 +33,7 @@ import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.c2.protocol.component.api.ComponentManifest;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowAnalysisRuleDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowRegistryClientDefinition;
@@ -679,6 +680,29 @@ public class ControllerFacade implements Authorizable {
return componentManifest.getFlowAnalysisRules().stream().filter(flowAnalysisRuleDefinition -> type.equals(flowAnalysisRuleDefinition.getType())).findFirst().orElse(null);
}
+ public ConnectorDefinition getConnectorDefinition(final String group, final String artifact, final String version, final String type) {
+ final ComponentManifest componentManifest = getComponentManifest(group, artifact, version);
+ final List<ConnectorDefinition> connectorDefinitions = componentManifest.getConnectors();
+ if (connectorDefinitions == null) {
+ return null;
+ }
+ final ConnectorDefinition connectorDefinition = connectorDefinitions.stream()
+ .filter(definition -> group.equals(definition.getGroup())
+ && artifact.equals(definition.getArtifact())
+ && version.equals(definition.getVersion())
+ && type.equals(definition.getType()))
+ .findFirst()
+ .orElse(null);
+
+ if (connectorDefinition != null && connectorDefinition.getConfigurationSteps() != null) {
+ final Map<String, File> stepDocumentation = runtimeManifestService.discoverStepDocumentation(group, artifact, version, type);
+ final Set<String> documentedSteps = stepDocumentation.keySet();
+ connectorDefinition.getConfigurationSteps().forEach(step -> step.setDocumented(documentedSteps.contains(step.getName())));
+ }
+
+ return connectorDefinition;
+ }
+
public String getAdditionalDetails(String group, String artifact, String version, String type) {
final Map<String, File> additionalDetailsMap = runtimeManifestService.discoverAdditionalDetails(group, artifact, version);
final File additionalDetailsFile = additionalDetailsMap.get(type);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java
index 9152558394..6374029cac 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java
@@ -19,7 +19,12 @@ package org.apache.nifi.web.controller;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.c2.protocol.component.api.Bundle;
+import org.apache.nifi.c2.protocol.component.api.ComponentManifest;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.manifest.RuntimeManifestService;
import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
import org.apache.nifi.web.search.query.SearchQuery;
import org.apache.nifi.web.search.query.SearchQueryParser;
@@ -31,7 +36,12 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.context.SecurityContextHolder;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -54,13 +64,31 @@ public class ControllerFacadeTest {
@Mock
private SearchQuery searchQuery;
+ @Mock
+ private RuntimeManifestService runtimeManifestService;
+
+ @Mock
+ private RuntimeManifest runtimeManifest;
+
+ @Mock
+ private Bundle manifestBundle;
+
+ @Mock
+ private ComponentManifest componentManifest;
+
private ControllerFacade controllerFacade;
+ private static final String TEST_GROUP = "org.apache.nifi";
+ private static final String TEST_ARTIFACT = "nifi-test-nar";
+ private static final String TEST_VERSION = "2.0.0";
+ private static final String TEST_CONNECTOR_TYPE = "org.apache.nifi.connectors.TestConnector";
+
@BeforeEach
public void setUp() {
controllerFacade = new ControllerFacade();
controllerFacade.setSearchQueryParser(searchQueryParser);
controllerFacade.setControllerSearchService(controllerSearchService);
+ controllerFacade.setRuntimeManifestService(runtimeManifestService);
final NiFiUser user = new StandardNiFiUser.Builder().identity("test-user").build();
SecurityContextHolder.getContext().setAuthentication(new NiFiAuthenticationToken(new NiFiUserDetails(user)));
@@ -100,5 +128,60 @@ public class ControllerFacadeTest {
assertNotNull(results);
verify(searchQueryParser).parse(eq(null), any(NiFiUser.class), eq(connectorProcessGroup), eq(connectorProcessGroup));
}
+
+ @Test
+ public void testGetConnectorDefinitionReturnsDefinitionWhenFound() {
+ final ConnectorDefinition expectedDefinition = new ConnectorDefinition();
+ expectedDefinition.setType(TEST_CONNECTOR_TYPE);
+
+ when(runtimeManifestService.getManifestForBundle(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION)).thenReturn(runtimeManifest);
+ when(runtimeManifest.getBundles()).thenReturn(List.of(manifestBundle));
+ when(manifestBundle.getComponentManifest()).thenReturn(componentManifest);
+ when(componentManifest.getConnectors()).thenReturn(List.of(expectedDefinition));
+
+ final ConnectorDefinition result = controllerFacade.getConnectorDefinition(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION, TEST_CONNECTOR_TYPE);
+
+ assertNotNull(result);
+ assertEquals(TEST_CONNECTOR_TYPE, result.getType());
+ }
+
+ @Test
+ public void testGetConnectorDefinitionReturnsNullWhenConnectorsListIsNull() {
+ when(runtimeManifestService.getManifestForBundle(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION)).thenReturn(runtimeManifest);
+ when(runtimeManifest.getBundles()).thenReturn(List.of(manifestBundle));
+ when(manifestBundle.getComponentManifest()).thenReturn(componentManifest);
+ when(componentManifest.getConnectors()).thenReturn(null);
+
+ final ConnectorDefinition result = controllerFacade.getConnectorDefinition(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION, TEST_CONNECTOR_TYPE);
+
+ assertNull(result);
+ }
+
+ @Test
+ public void testGetConnectorDefinitionReturnsNullWhenConnectorTypeNotFound() {
+ final ConnectorDefinition otherDefinition = new ConnectorDefinition();
+ otherDefinition.setType("org.apache.nifi.connectors.OtherConnector");
+
+ when(runtimeManifestService.getManifestForBundle(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION)).thenReturn(runtimeManifest);
+ when(runtimeManifest.getBundles()).thenReturn(List.of(manifestBundle));
+ when(manifestBundle.getComponentManifest()).thenReturn(componentManifest);
+ when(componentManifest.getConnectors()).thenReturn(List.of(otherDefinition));
+
+ final ConnectorDefinition result = controllerFacade.getConnectorDefinition(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION, TEST_CONNECTOR_TYPE);
+
+ assertNull(result);
+ }
+
+ @Test
+ public void testGetConnectorDefinitionReturnsNullWhenConnectorsListIsEmpty() {
+ when(runtimeManifestService.getManifestForBundle(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION)).thenReturn(runtimeManifest);
+ when(runtimeManifest.getBundles()).thenReturn(List.of(manifestBundle));
+ when(manifestBundle.getComponentManifest()).thenReturn(componentManifest);
+ when(componentManifest.getConnectors()).thenReturn(Collections.emptyList());
+
+ final ConnectorDefinition result = controllerFacade.getConnectorDefinition(TEST_GROUP, TEST_ARTIFACT, TEST_VERSION, TEST_CONNECTOR_TYPE);
+
+ assertNull(result);
+ }
}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation-routing.module.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation-routing.module.ts
index 544dd8106c..61abbbff12 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation-routing.module.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation-routing.module.ts
@@ -26,6 +26,7 @@ import { ParameterProviderDefinition } from '../ui/parameter-provider-definition
import { FlowAnalysisRuleDefinition } from '../ui/flow-analysis-rule-definition/flow-analysis-rule-definition.component';
import { Overview } from '../ui/overview/overview.component';
import { FlowRegistryClientDefinition } from '../ui/flow-registry-client-definition/flow-registry-client-definition.component';
+import { ConnectorDefinitionComponent } from '../ui/connector-definition/connector-definition.component';
const routes: Routes = [
{
@@ -57,6 +58,10 @@ const routes: Routes = [
path: `${ComponentType.FlowAnalysisRule}/:group/:artifact/:version/:type`,
component: FlowAnalysisRuleDefinition
},
+ {
+ path: `${ComponentType.Connector}/:group/:artifact/:version/:type`,
+ component: ConnectorDefinitionComponent
+ },
{
path: 'overview',
component: Overview
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.component.html b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.component.html
index 22109056a2..0174272d21 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.component.html
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.component.html
@@ -125,6 +125,19 @@
}
"></ng-container>
</mat-expansion-panel>
+ <mat-expansion-panel [expanded]="connectorsExpanded">
+ <mat-expansion-panel-header>
+ <mat-panel-title>Connectors</mat-panel-title>
+ </mat-expansion-panel-header>
+ <ng-container
+ *ngTemplateOutlet="
+ extensionLinks;
+ context: {
+ $implicit: filterExtensions((connectorTypes$ | async)!),
+ componentType: ComponentType.Connector
+ }
+ "></ng-container>
+ </mat-expansion-panel>
</mat-accordion>
<ng-template #extensionLinks let-extensionTypes let-componentType="componentType">
@if (extensionTypes.length === 0) {
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.component.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.component.ts
index ff14eb6379..332d9d74eb 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.component.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.component.ts
@@ -29,6 +29,7 @@ import { NiFiState } from '../../../state';
import { Store } from '@ngrx/store';
import { loadExtensionTypesForDocumentation } from '../../../state/extension-types/extension-types.actions';
import {
+ selectConnectorTypes,
selectControllerServiceTypes,
selectFlowAnalysisRuleTypes,
selectParameterProviderTypes,
@@ -82,6 +83,9 @@ export class Documentation implements OnInit, AfterViewInit {
flowAnalysisRuleTypes$ = this.store
.select(selectFlowAnalysisRuleTypes)
.pipe(map((extensionTypes) => this.sortExtensions(extensionTypes)));
+ connectorTypes$ = this.store
+ .select(selectConnectorTypes)
+ .pipe(map((extensionTypes) => this.sortExtensions(extensionTypes)));
accordion = viewChild.required(MatAccordion);
@@ -98,6 +102,7 @@ export class Documentation implements OnInit, AfterViewInit {
reportingTasksExpanded = false;
parameterProvidersExpanded = false;
registryClientsExpanded = false;
+ connectorsExpanded = false;
constructor() {
this.store
@@ -135,6 +140,9 @@ export class Documentation implements OnInit, AfterViewInit {
case ComponentType.FlowRegistryClient:
this.registryClientsExpanded = true;
break;
+ case ComponentType.Connector:
+ this.connectorsExpanded = true;
+ break;
}
});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.module.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.module.ts
index 0766ac00a8..5c3f046090 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.module.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/feature/documentation.module.ts
@@ -36,6 +36,7 @@ import { ReportingTaskDefinitionEffects } from '../state/reporting-task-definiti
import { ParameterProviderDefinitionEffects } from '../state/parameter-provider-definition/parameter-provider-definition.effects';
import { FlowAnalysisRuleDefinitionEffects } from '../state/flow-analysis-rule-definition/flow-analysis-rule-definition.effects';
import { FlowRegistryClientDefinitionEffects } from '../state/flow-registry-client-definition/flow-registry-client-definition.effects';
+import { ConnectorDefinitionEffects } from '../state/connector-definition/connector-definition.effects';
@NgModule({
declarations: [Documentation],
@@ -51,6 +52,7 @@ import { FlowRegistryClientDefinitionEffects } from '../state/flow-registry-clie
FlowRegistryClientDefinitionEffects,
ParameterProviderDefinitionEffects,
FlowAnalysisRuleDefinitionEffects,
+ ConnectorDefinitionEffects,
AdditionalDetailsEffects,
DocumentationEffects
),
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/service/documentation.service.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/service/documentation.service.ts
index a9955bf6bd..2b7c12f2ab 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/service/documentation.service.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/service/documentation.service.ts
@@ -26,6 +26,7 @@ import { ReportingTaskDefinition } from '../state/reporting-task-definition';
import { ParameterProviderDefinition } from '../state/parameter-provider-definition';
import { FlowAnalysisRuleDefinition } from '../state/flow-analysis-rule-definition';
import { FlowRegistryClientDefinition } from '../state/flow-registry-client-definition';
+import { ConnectorDefinition } from '../state/connector-definition';
@Injectable({ providedIn: 'root' })
export class DocumentationService {
@@ -74,4 +75,20 @@ export class DocumentationService {
`${DocumentationService.API}/flow/additional-details/${coordinates.group}/${coordinates.artifact}/${coordinates.version}/${coordinates.type}`
);
}
+
+ getConnectorDefinition(coordinates: DefinitionCoordinates): Observable<ConnectorDefinition> {
+ return this.httpClient.get<ConnectorDefinition>(
+ `${DocumentationService.API}/flow/connector-definition/${coordinates.group}/${coordinates.artifact}/${coordinates.version}/${coordinates.type}`
+ );
+ }
+
+ getStepDocumentation(coordinates: DefinitionCoordinates, stepName: string): Observable<StepDocumentationEntity> {
+ return this.httpClient.get<StepDocumentationEntity>(
+ `${DocumentationService.API}/flow/steps/${coordinates.group}/${coordinates.artifact}/${coordinates.version}/${coordinates.type}/${stepName}`
+ );
+ }
+}
+
+export interface StepDocumentationEntity {
+ stepDocumentation: string;
}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.actions.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.actions.ts
new file mode 100644
index 0000000000..aaa80ac1b1
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.actions.ts
@@ -0,0 +1,56 @@
+/*
+ * 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 { createAction, props } from '@ngrx/store';
+import { ConnectorDefinition } from './index';
+import { DefinitionCoordinates } from '../index';
+
+const CONNECTOR_DEFINITION_PREFIX = '[Connector Definition]';
+
+export const loadConnectorDefinition = createAction(
+ `${CONNECTOR_DEFINITION_PREFIX} Load Connector Definition`,
+ props<{ coordinates: DefinitionCoordinates }>()
+);
+
+export const loadConnectorDefinitionSuccess = createAction(
+ `${CONNECTOR_DEFINITION_PREFIX} Load Connector Definition Success`,
+ props<{ connectorDefinition: ConnectorDefinition }>()
+);
+
+export const connectorDefinitionApiError = createAction(
+ `${CONNECTOR_DEFINITION_PREFIX} Load Connector Definition Error`,
+ props<{ error: string }>()
+);
+
+export const resetConnectorDefinitionState = createAction(
+ `${CONNECTOR_DEFINITION_PREFIX} Reset Connector Definition State`
+);
+
+export const loadStepDocumentation = createAction(
+ `${CONNECTOR_DEFINITION_PREFIX} Load Step Documentation`,
+ props<{ coordinates: DefinitionCoordinates; stepName: string }>()
+);
+
+export const loadStepDocumentationSuccess = createAction(
+ `${CONNECTOR_DEFINITION_PREFIX} Load Step Documentation Success`,
+ props<{ stepName: string; documentation: string }>()
+);
+
+export const stepDocumentationApiError = createAction(
+ `${CONNECTOR_DEFINITION_PREFIX} Load Step Documentation Error`,
+ props<{ stepName: string; error: string }>()
+);
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.effects.ts
new file mode 100644
index 0000000000..e4ebac8ffc
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.effects.ts
@@ -0,0 +1,79 @@
+/*
+ * 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 { Injectable, inject } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import * as ConnectorDefinitionActions from './connector-definition.actions';
+import { catchError, from, map, mergeMap, of, switchMap } from 'rxjs';
+import { DocumentationService } from '../../service/documentation.service';
+import { ConnectorDefinition } from './index';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+
+@Injectable()
+export class ConnectorDefinitionEffects {
+ private actions$ = inject(Actions);
+ private documentationService = inject(DocumentationService);
+ private errorHelper = inject(ErrorHelper);
+
+ loadConnectorDefinition$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ConnectorDefinitionActions.loadConnectorDefinition),
+ map((action) => action.coordinates),
+ switchMap((coordinates) =>
+ from(this.documentationService.getConnectorDefinition(coordinates)).pipe(
+ map((connectorDefinition: ConnectorDefinition) =>
+ ConnectorDefinitionActions.loadConnectorDefinitionSuccess({
+ connectorDefinition
+ })
+ ),
+ catchError((errorResponse: HttpErrorResponse) =>
+ of(
+ ConnectorDefinitionActions.connectorDefinitionApiError({
+ error: this.errorHelper.getErrorString(errorResponse)
+ })
+ )
+ )
+ )
+ )
+ )
+ );
+
+ loadStepDocumentation$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ConnectorDefinitionActions.loadStepDocumentation),
+ mergeMap((action) =>
+ from(this.documentationService.getStepDocumentation(action.coordinates, action.stepName)).pipe(
+ map((response) =>
+ ConnectorDefinitionActions.loadStepDocumentationSuccess({
+ stepName: action.stepName,
+ documentation: response.stepDocumentation
+ })
+ ),
+ catchError((errorResponse: HttpErrorResponse) =>
+ of(
+ ConnectorDefinitionActions.stepDocumentationApiError({
+ stepName: action.stepName,
+ error: this.errorHelper.getErrorString(errorResponse)
+ })
+ )
+ )
+ )
+ )
+ )
+ );
+}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.reducer.ts
new file mode 100644
index 0000000000..e2953573dd
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.reducer.ts
@@ -0,0 +1,92 @@
+/*
+ * 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 { createReducer, on } from '@ngrx/store';
+import { ConnectorDefinitionState } from './index';
+import {
+ connectorDefinitionApiError,
+ loadConnectorDefinition,
+ loadConnectorDefinitionSuccess,
+ loadStepDocumentation,
+ loadStepDocumentationSuccess,
+ resetConnectorDefinitionState,
+ stepDocumentationApiError
+} from './connector-definition.actions';
+
+export const initialConnectorDefinitionState: ConnectorDefinitionState = {
+ connectorDefinition: null,
+ error: null,
+ status: 'pending',
+ stepDocumentation: {}
+};
+
+export const connectorDefinitionReducer = createReducer(
+ initialConnectorDefinitionState,
+ on(loadConnectorDefinition, (state) => ({
+ ...state,
+ connectorDefinition: null,
+ error: null,
+ status: 'loading' as const
+ })),
+ on(loadConnectorDefinitionSuccess, (state, { connectorDefinition }) => ({
+ ...state,
+ connectorDefinition,
+ error: null,
+ status: 'success' as const
+ })),
+ on(connectorDefinitionApiError, (state, { error }) => ({
+ ...state,
+ error,
+ status: 'error' as const
+ })),
+ on(resetConnectorDefinitionState, () => ({
+ ...initialConnectorDefinitionState
+ })),
+ on(loadStepDocumentation, (state, { stepName }) => ({
+ ...state,
+ stepDocumentation: {
+ ...state.stepDocumentation,
+ [stepName]: {
+ documentation: null,
+ error: null,
+ status: 'loading' as const
+ }
+ }
+ })),
+ on(loadStepDocumentationSuccess, (state, { stepName, documentation }) => ({
+ ...state,
+ stepDocumentation: {
+ ...state.stepDocumentation,
+ [stepName]: {
+ documentation,
+ error: null,
+ status: 'success' as const
+ }
+ }
+ })),
+ on(stepDocumentationApiError, (state, { stepName, error }) => ({
+ ...state,
+ stepDocumentation: {
+ ...state.stepDocumentation,
+ [stepName]: {
+ documentation: null,
+ error,
+ status: 'error' as const
+ }
+ }
+ }))
+);
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.selectors.ts
similarity index 54%
copy from nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
copy to nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.selectors.ts
index 0413e260ed..ad0adc5060 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/connector-definition.selectors.ts
@@ -6,7 +6,7 @@
* (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
+ * 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,
@@ -14,23 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.extension.manifest;
-/**
- * Possible types of extensions.
- */
-public enum ExtensionType {
-
- PROCESSOR,
-
- CONTROLLER_SERVICE,
-
- REPORTING_TASK,
-
- FLOW_ANALYSIS_RULE,
-
- PARAMETER_PROVIDER,
+import { createSelector } from '@ngrx/store';
+import { DocumentationState, selectDocumentationState } from '../index';
+import { connectorDefinitionFeatureKey, StepDocumentationState } from './index';
- FLOW_REGISTRY_CLIENT;
+export const selectConnectorDefinitionState = createSelector(
+ selectDocumentationState,
+ (state: DocumentationState) => state[connectorDefinitionFeatureKey]
+);
-}
+export const selectStepDocumentation = (stepName: string) =>
+ createSelector(
+ selectConnectorDefinitionState,
+ (state): StepDocumentationState | undefined => state.stepDocumentation[stepName]
+ );
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/index.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/index.ts
new file mode 100644
index 0000000000..fda3d93c8b
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/connector-definition/index.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { ExtensionComponent, PropertyAllowableValue } from '../index';
+
+export const connectorDefinitionFeatureKey = 'connectorDefinition';
+
+export interface ConnectorPropertyDependency {
+ propertyName: string;
+ dependentValues?: string[];
+}
+
+export interface ConnectorPropertyDescriptor {
+ name: string;
+ description: string;
+ defaultValue?: string;
+ required: boolean;
+ propertyType?: string;
+ allowableValuesFetchable: boolean;
+ allowableValues?: PropertyAllowableValue[];
+ dependencies?: ConnectorPropertyDependency[];
+}
+
+export interface ConnectorPropertyGroup {
+ name: string;
+ description: string;
+ properties?: ConnectorPropertyDescriptor[];
+}
+
+export interface ConfigurationStepDependency {
+ stepName: string;
+ propertyName: string;
+ dependentValues?: string[];
+}
+
+export interface ConfigurationStep {
+ name: string;
+ description: string;
+ documented: boolean;
+ stepDependencies?: ConfigurationStepDependency[];
+ propertyGroups?: ConnectorPropertyGroup[];
+}
+
+export interface ConnectorDefinition extends ExtensionComponent {
+ configurationSteps?: ConfigurationStep[];
+}
+
+export interface StepDocumentationState {
+ documentation: string | null;
+ error: string | null;
+ status: 'pending' | 'loading' | 'success' | 'error';
+}
+
+export interface ConnectorDefinitionState {
+ connectorDefinition: ConnectorDefinition | null;
+ error: string | null;
+ status: 'pending' | 'loading' | 'success' | 'error';
+ stepDocumentation: { [stepName: string]: StepDocumentationState };
+}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/index.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/index.ts
index 0e81eab644..65d380edb7 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/index.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/state/index.ts
@@ -41,6 +41,8 @@ import {
import { parameterProviderDefinitionReducer } from './parameter-provider-definition/parameter-provider-definition.reducer';
import { flowAnalysisRuleDefinitionFeatureKey, FlowAnalysisRuleDefinitionState } from './flow-analysis-rule-definition';
import { flowAnalysisRuleDefinitionReducer } from './flow-analysis-rule-definition/flow-analysis-rule-definition.reducer';
+import { connectorDefinitionFeatureKey, ConnectorDefinitionState } from './connector-definition';
+import { connectorDefinitionReducer } from './connector-definition/connector-definition.reducer';
import { ComponentType } from '@nifi/shared';
import { DocumentedType } from '../../../state/shared';
@@ -196,6 +198,7 @@ export interface DocumentationState {
[flowRegistryClientDefinitionFeatureKey]: FlowRegistryClientDefinitionState;
[parameterProviderDefinitionFeatureKey]: ParameterProviderDefinitionState;
[flowAnalysisRuleDefinitionFeatureKey]: FlowAnalysisRuleDefinitionState;
+ [connectorDefinitionFeatureKey]: ConnectorDefinitionState;
[additionalDetailsFeatureKey]: AdditionalDetailsState;
[externalDocumentationFeatureKey]: ExternalDocumentationState;
}
@@ -208,6 +211,7 @@ export function reducers(state: DocumentationState | undefined, action: Action)
[flowRegistryClientDefinitionFeatureKey]: flowRegistryClientDefinitionReducer,
[parameterProviderDefinitionFeatureKey]: parameterProviderDefinitionReducer,
[flowAnalysisRuleDefinitionFeatureKey]: flowAnalysisRuleDefinitionReducer,
+ [connectorDefinitionFeatureKey]: connectorDefinitionReducer,
[additionalDetailsFeatureKey]: additionalDetailsReducer,
[externalDocumentationFeatureKey]: externalDocumentationReducer
})(state, action);
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.html b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.html
new file mode 100644
index 0000000000..395c7fff9d
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.html
@@ -0,0 +1,95 @@
+<!--
+ ~ 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.
+ -->
+
+@if (propertyDescriptor) {
+ <div class="connector-property-definition flex flex-col gap-y-4">
+ <div>
+ <div>Name</div>
+ <div class="tertiary-color font-medium">{{ propertyDescriptor.name }}</div>
+ </div>
+ <div>
+ <div>Description</div>
+ <div class="tertiary-color font-medium">{{ propertyDescriptor.description }}</div>
+ </div>
+ <div>
+ <div>Type</div>
+ <div class="tertiary-color font-medium">{{ formatPropertyType() }}</div>
+ </div>
+ <div>
+ <div>Required</div>
+ <div class="tertiary-color font-medium">{{ propertyDescriptor.required ? 'Yes' : 'No' }}</div>
+ </div>
+ @if (formatDefaultValue(); as defaultValue) {
+ <div>
+ <div>Default Value</div>
+ <div class="tertiary-color font-medium">{{ defaultValue }}</div>
+ </div>
+ }
+ @if (propertyDescriptor.allowableValues) {
+ <div>
+ <div>Allowable Values</div>
+ <div class="tertiary-color font-medium">
+ <ul class="list-disc list-inside">
+ @for (allowableValue of propertyDescriptor.allowableValues; track allowableValue.value) {
+ <li>
+ {{ allowableValue.displayName }}
+ @if (allowableValue.description) {
+ <i
+ class="fa fa-info-circle primary-color"
+ nifiTooltip
+ [tooltipComponentType]="TextTip"
+ [tooltipInputData]="allowableValue.description"
+ [delayClose]="false"></i>
+ }
+ </li>
+ }
+ </ul>
+ </div>
+ </div>
+ }
+ @if (propertyDescriptor.dependencies) {
+ <div>
+ <div>
+ Dependencies
+ <i
+ class="fa fa-info-circle primary-color"
+ nifiTooltip
+ [tooltipComponentType]="TextTip"
+ tooltipInputData="All dependencies must be satisfied"></i>
+ </div>
+ <div class="tertiary-color font-medium">
+ <ul class="list-disc list-inside">
+ @for (
+ propertyDependency of sortDependencies(propertyDescriptor.dependencies);
+ track propertyDependency.propertyName
+ ) {
+ <li>
+ @if (propertyDependency.dependentValues) {
+ {{ propertyDependency.propertyName }} is set to any of [{{
+ formatDependentValue(propertyDependency)
+ }}]
+ } @else {
+ {{ propertyDependency.propertyName }} has any value specified
+ }
+ </li>
+ }
+ </ul>
+ </div>
+ </div>
+ }
+ </div>
+}
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.scss
similarity index 71%
copy from nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
copy to nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.scss
index 0413e260ed..eb0ceabcbd 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.scss
@@ -6,7 +6,7 @@
* (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
+ * 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,
@@ -14,23 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.extension.manifest;
-
-/**
- * Possible types of extensions.
- */
-public enum ExtensionType {
-
- PROCESSOR,
-
- CONTROLLER_SERVICE,
-
- REPORTING_TASK,
-
- FLOW_ANALYSIS_RULE,
-
- PARAMETER_PROVIDER,
-
- FLOW_REGISTRY_CLIENT;
+:host {
+ display: block;
}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.spec.ts
new file mode 100644
index 0000000000..45143688eb
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.spec.ts
@@ -0,0 +1,176 @@
+/*
+ * 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 { TestBed } from '@angular/core/testing';
+
+import { ConnectorPropertyDefinitionComponent } from './connector-property-definition.component';
+import { ConnectorPropertyDescriptor } from '../../../state/connector-definition';
+
+describe('ConnectorPropertyDefinitionComponent', () => {
+ function createMockProperty(overrides?: Partial<ConnectorPropertyDescriptor>): ConnectorPropertyDescriptor {
+ return {
+ name: 'Test Property',
+ description: 'A test property description',
+ required: false,
+ allowableValuesFetchable: false,
+ ...overrides
+ };
+ }
+
+ async function setup(propertyOverrides?: Partial<ConnectorPropertyDescriptor>) {
+ await TestBed.configureTestingModule({
+ imports: [ConnectorPropertyDefinitionComponent]
+ }).compileComponents();
+
+ const fixture = TestBed.createComponent(ConnectorPropertyDefinitionComponent);
+ const component = fixture.componentInstance;
+ component.propertyDescriptor = createMockProperty(propertyOverrides);
+ fixture.detectChanges();
+
+ return { component, fixture };
+ }
+
+ describe('Component initialization', () => {
+ it('should create', async () => {
+ const { component } = await setup();
+ expect(component).toBeTruthy();
+ });
+
+ it('should have property descriptor set', async () => {
+ const { component } = await setup({ name: 'Custom Property' });
+ expect(component.propertyDescriptor.name).toBe('Custom Property');
+ });
+ });
+
+ describe('Default value formatting', () => {
+ it('should return undefined when no default value is set', async () => {
+ const { component } = await setup({ defaultValue: undefined });
+ expect(component.formatDefaultValue()).toBeUndefined();
+ });
+
+ it('should return the default value as-is when no allowable values exist', async () => {
+ const { component } = await setup({ defaultValue: 'my-default' });
+ expect(component.formatDefaultValue()).toBe('my-default');
+ });
+
+ it('should return the display name when default value matches an allowable value', async () => {
+ const { component } = await setup({
+ defaultValue: 'val1',
+ allowableValues: [
+ { value: 'val1', displayName: 'Value One', description: 'First value' },
+ { value: 'val2', displayName: 'Value Two', description: 'Second value' }
+ ]
+ });
+ expect(component.formatDefaultValue()).toBe('Value One');
+ });
+
+ it('should return the raw default value when it does not match any allowable value', async () => {
+ const { component } = await setup({
+ defaultValue: 'unknown',
+ allowableValues: [{ value: 'val1', displayName: 'Value One', description: 'First value' }]
+ });
+ expect(component.formatDefaultValue()).toBe('unknown');
+ });
+ });
+
+ describe('Property type formatting', () => {
+ it('should return STRING when property type is undefined', async () => {
+ const { component } = await setup({ propertyType: undefined });
+ expect(component.formatPropertyType()).toBe('STRING');
+ });
+
+ it('should return the property type when defined', async () => {
+ const { component } = await setup({ propertyType: 'INTEGER' });
+ expect(component.formatPropertyType()).toBe('INTEGER');
+ });
+ });
+
+ describe('Dependency sorting', () => {
+ it('should sort dependencies alphabetically by property name', async () => {
+ const { component } = await setup();
+ const dependencies = [
+ { propertyName: 'zeta', dependentValues: ['a'] },
+ { propertyName: 'alpha', dependentValues: ['b'] },
+ { propertyName: 'gamma', dependentValues: ['c'] }
+ ];
+
+ const sorted = component.sortDependencies(dependencies);
+
+ expect(sorted[0].propertyName).toBe('alpha');
+ expect(sorted[1].propertyName).toBe('gamma');
+ expect(sorted[2].propertyName).toBe('zeta');
+ });
+
+ it('should not mutate the original array', async () => {
+ const { component } = await setup();
+ const dependencies = [
+ { propertyName: 'zeta', dependentValues: ['a'] },
+ { propertyName: 'alpha', dependentValues: ['b'] }
+ ];
+
+ component.sortDependencies(dependencies);
+
+ expect(dependencies[0].propertyName).toBe('zeta');
+ });
+ });
+
+ describe('Dependent value formatting', () => {
+ it('should return empty string when dependentValues is undefined', async () => {
+ const { component } = await setup();
+ const dependency = { propertyName: 'prop1' };
+
+ expect(component.formatDependentValue(dependency)).toBe('');
+ });
+
+ it('should join dependent values with comma', async () => {
+ const { component } = await setup();
+ const dependency = { propertyName: 'prop1', dependentValues: ['val1', 'val2', 'val3'] };
+
+ expect(component.formatDependentValue(dependency)).toBe('val1, val2, val3');
+ });
+
+ it('should use display names from lookupProperty when available', async () => {
+ const { component } = await setup();
+ const properties: ConnectorPropertyDescriptor[] = [
+ {
+ name: 'dep-prop',
+ description: 'Dependency property',
+ required: false,
+ allowableValuesFetchable: false,
+ allowableValues: [
+ { value: 'v1', displayName: 'Display Value 1', description: '' },
+ { value: 'v2', displayName: 'Display Value 2', description: '' }
+ ]
+ }
+ ];
+ component.lookupProperty = (name: string) => properties.find((p) => p.name === name);
+
+ const dependency = { propertyName: 'dep-prop', dependentValues: ['v1', 'v2'] };
+
+ expect(component.formatDependentValue(dependency)).toBe('Display Value 1, Display Value 2');
+ });
+
+ it('should fall back to raw values when lookupProperty does not find the property', async () => {
+ const { component } = await setup();
+ component.lookupProperty = () => undefined;
+
+ const dependency = { propertyName: 'unknown-prop', dependentValues: ['raw1', 'raw2'] };
+
+ expect(component.formatDependentValue(dependency)).toBe('raw1, raw2');
+ });
+ });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.ts
new file mode 100644
index 0000000000..fae2f00945
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/common/connector-property-definition/connector-property-definition.component.ts
@@ -0,0 +1,88 @@
+/*
+ * 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 { Component, Input, inject } from '@angular/core';
+import { NiFiCommon, NifiTooltipDirective, TextTip } from '@nifi/shared';
+import { ConnectorPropertyDescriptor, ConnectorPropertyDependency } from '../../../state/connector-definition';
+import { PropertyAllowableValue } from '../../../state';
+
+@Component({
+ selector: 'connector-property-definition',
+ imports: [NifiTooltipDirective],
+ templateUrl: './connector-property-definition.component.html',
+ styleUrl: './connector-property-definition.component.scss'
+})
+export class ConnectorPropertyDefinitionComponent {
+ private nifiCommon = inject(NiFiCommon);
+
+ @Input() propertyDescriptor!: ConnectorPropertyDescriptor;
+ @Input() lookupProperty: ((name: string) => ConnectorPropertyDescriptor | undefined) | undefined;
+
+ protected readonly TextTip = TextTip;
+
+ formatDefaultValue(): string | undefined {
+ if (!this.propertyDescriptor.defaultValue) {
+ return undefined;
+ }
+
+ if (this.propertyDescriptor.allowableValues) {
+ const defaultAllowableValue: PropertyAllowableValue | undefined =
+ this.propertyDescriptor.allowableValues.find(
+ (allowableValue) => allowableValue.value === this.propertyDescriptor.defaultValue
+ );
+
+ if (defaultAllowableValue) {
+ return defaultAllowableValue.displayName;
+ }
+ }
+
+ return this.propertyDescriptor.defaultValue;
+ }
+
+ formatPropertyType(): string {
+ return this.propertyDescriptor.propertyType || 'STRING';
+ }
+
+ sortDependencies(dependencies: ConnectorPropertyDependency[]): ConnectorPropertyDependency[] {
+ return dependencies.slice().sort((a, b) => {
+ return this.nifiCommon.compareString(a.propertyName, b.propertyName);
+ });
+ }
+
+ formatDependentValue(dependency: ConnectorPropertyDependency): string {
+ if (dependency.dependentValues) {
+ if (this.lookupProperty) {
+ const propertyDependency = this.lookupProperty(dependency.propertyName);
+
+ if (propertyDependency?.allowableValues) {
+ return dependency.dependentValues
+ .map((dependentValue) => {
+ const dependentAllowableValue = propertyDependency.allowableValues?.find(
+ (allowableValue) => dependentValue === allowableValue.value
+ );
+ return dependentAllowableValue ? dependentAllowableValue.displayName : dependentValue;
+ })
+ .join(', ');
+ }
+ }
+
+ return dependency.dependentValues.join(', ');
+ }
+
+ return '';
+ }
+}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.html b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.html
new file mode 100644
index 0000000000..849ce1db73
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.html
@@ -0,0 +1,209 @@
+<!--
+ ~ 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.
+ -->
+
+@if (connectorDefinitionState) {
+ @if (isInitialLoading(connectorDefinitionState)) {
+ <ngx-skeleton-loader count="3"></ngx-skeleton-loader>
+ } @else {
+ @if (connectorDefinitionState.connectorDefinition; as connectorDefinition) {
+ <div class="flex flex-col gap-y-4 p-4">
+ <h3 class="primary-color">
+ {{ formatExtensionName(connectorDefinition.type) }} {{ connectorDefinition.version }}
+ </h3>
+ <div class="flex flex-col gap-y-4">
+ @if (connectorDefinition.deprecated) {
+ <div class="caution-color-background p-4 flex flex-col gap-y-2">
+ @if (connectorDefinition.deprecationReason) {
+ <div>{{ connectorDefinition.deprecationReason }}</div>
+ } @else {
+ <div>
+ Please be aware this connector is deprecated and may be removed in the near future.
+ </div>
+ }
+ @if (connectorDefinition.deprecationAlternatives) {
+ <div>
+ <div>See Alternatives</div>
+ <see-also [extensionTypes]="connectorDefinition.deprecationAlternatives"></see-also>
+ </div>
+ }
+ </div>
+ }
+ <div>
+ <div>Bundle</div>
+ <div class="tertiary-color font-medium">
+ {{ connectorDefinition.group }} | {{ connectorDefinition.artifact }}
+ </div>
+ </div>
+ @if (connectorDefinition.typeDescription) {
+ <div>
+ <div>Description</div>
+ <div class="tertiary-color font-medium">{{ connectorDefinition.typeDescription }}</div>
+ </div>
+ }
+ @if (connectorDefinition.tags) {
+ <div>
+ <div>Tags</div>
+ <div class="tertiary-color font-medium">
+ {{ connectorDefinition.tags.join(', ') }}
+ </div>
+ </div>
+ }
+ @if (hasConfigurationSteps(connectorDefinition.configurationSteps)) {
+ <div>
+ <div class="flex justify-between items-center">
+ <h2>Configuration Steps</h2>
+ <div>
+ <button
+ type="button"
+ mat-icon-button
+ class="primary-icon-button"
+ title="Expand All"
+ (click)="expandAllSteps()">
+ <i class="fa fa-expand fa-rotate-45"></i>
+ </button>
+ <button
+ type="button"
+ mat-icon-button
+ class="primary-icon-button"
+ title="Collapse All"
+ (click)="collapseAllSteps()">
+ <i class="fa fa-compress fa-rotate-45"></i>
+ </button>
+ </div>
+ </div>
+ <mat-accordion multi #stepsAccordion="matAccordion">
+ @for (
+ step of connectorDefinition.configurationSteps;
+ track step.name;
+ let first = $first
+ ) {
+ <mat-expansion-panel
+ [expanded]="first"
+ (afterExpand)="onStepExpanded(connectorDefinition, step)">
+ <mat-expansion-panel-header>
+ <mat-panel-title>{{ step.name }}</mat-panel-title>
+ </mat-expansion-panel-header>
+ <div class="flex flex-col gap-y-4">
+ @if (step.description) {
+ <div class="tertiary-color">{{ step.description }}</div>
+ }
+ @if (step.documented) {
+ <mat-expansion-panel [expanded]="true">
+ <mat-expansion-panel-header>
+ <mat-panel-title>Step Documentation</mat-panel-title>
+ </mat-expansion-panel-header>
+ <div class="step-documentation">
+ @if (isStepDocumentationLoading(step.name)) {
+ <ngx-skeleton-loader count="3"></ngx-skeleton-loader>
+ } @else if (getStepDocumentationError(step.name); as error) {
+ <div>{{ error }}</div>
+ } @else if (getStepDocumentation(step.name); as documentation) {
+ <markdown [data]="documentation"></markdown>
+ }
+ </div>
+ </mat-expansion-panel>
+ }
+ @if (hasPropertyGroups(step.propertyGroups)) {
+ @for (group of step.propertyGroups; track group.name) {
+ <div class="property-group flex flex-col gap-y-2 mb-4">
+ <div class="flex justify-between items-center">
+ <h4>{{ group.name }}</h4>
+ </div>
+ @if (group.description) {
+ <div class="tertiary-color">
+ {{ group.description }}
+ </div>
+ }
+ @if (hasProperties(group.properties)) {
+ <div class="flex justify-between items-center">
+ <div class="tertiary-color">
+ In the list below required properties are shown with
+ an asterisk (*). Other properties are considered
+ optional.
+ </div>
+ <div>
+ <button
+ type="button"
+ mat-icon-button
+ class="primary-icon-button"
+ title="Expand All"
+ (click)="
+ expandAllProperties(propertiesAccordion)
+ ">
+ <i class="fa fa-expand fa-rotate-45"></i>
+ </button>
+ <button
+ type="button"
+ mat-icon-button
+ class="primary-icon-button"
+ title="Collapse All"
+ (click)="
+ collapseAllProperties(propertiesAccordion)
+ ">
+ <i class="fa fa-compress fa-rotate-45"></i>
+ </button>
+ </div>
+ </div>
+ <mat-accordion multi #propertiesAccordion="matAccordion">
+ @for (
+ property of group.properties;
+ track property.name
+ ) {
+ <mat-expansion-panel>
+ <mat-expansion-panel-header>
+ <mat-panel-title
+ [title]="formatPropertyTitle(property)">
+ {{ formatPropertyTitle(property) }}
+ </mat-panel-title>
+ <mat-panel-description
+ [title]="property.description">
+ {{ property.description }}
+ </mat-panel-description>
+ </mat-expansion-panel-header>
+ <connector-property-definition
+ [propertyDescriptor]="property"
+ [lookupProperty]="
+ lookupProperty(group.properties!)
+ "></connector-property-definition>
+ </mat-expansion-panel>
+ }
+ </mat-accordion>
+ }
+ </div>
+ }
+ }
+ </div>
+ </mat-expansion-panel>
+ }
+ </mat-accordion>
+ </div>
+ }
+ @if (connectorDefinition.seeAlso) {
+ <div>
+ <h2>See Also</h2>
+ <see-also [extensionTypes]="connectorDefinition.seeAlso"></see-also>
+ </div>
+ }
+ </div>
+ </div>
+ } @else if (connectorDefinitionState.error) {
+ <div class="p-4">
+ {{ connectorDefinitionState.error }}
+ </div>
+ }
+ }
+}
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.scss
similarity index 71%
copy from nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
copy to nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.scss
index 0413e260ed..eb0ceabcbd 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.scss
@@ -6,7 +6,7 @@
* (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
+ * 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,
@@ -14,23 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.extension.manifest;
-
-/**
- * Possible types of extensions.
- */
-public enum ExtensionType {
-
- PROCESSOR,
-
- CONTROLLER_SERVICE,
-
- REPORTING_TASK,
-
- FLOW_ANALYSIS_RULE,
-
- PARAMETER_PROVIDER,
-
- FLOW_REGISTRY_CLIENT;
+:host {
+ display: block;
}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.spec.ts
new file mode 100644
index 0000000000..41ac8cac5f
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.spec.ts
@@ -0,0 +1,415 @@
+/*
+ * 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 { TestBed } from '@angular/core/testing';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+
+import { ConnectorDefinitionComponent } from './connector-definition.component';
+import {
+ ConfigurationStep,
+ connectorDefinitionFeatureKey,
+ ConnectorDefinitionState,
+ ConnectorPropertyDescriptor,
+ ConnectorPropertyGroup
+} from '../../state/connector-definition';
+import { initialConnectorDefinitionState } from '../../state/connector-definition/connector-definition.reducer';
+import { documentationFeatureKey } from '../../state';
+import { initialState as initialErrorState } from '../../../../state/error/error.reducer';
+import { errorFeatureKey } from '../../../../state/error';
+import { initialState as initialCurrentUserState } from '../../../../state/current-user/current-user.reducer';
+import { currentUserFeatureKey } from '../../../../state/current-user';
+import { ConnectorDefinition } from '../../state/connector-definition';
+import { loadStepDocumentation } from '../../state/connector-definition/connector-definition.actions';
+
+describe('ConnectorDefinitionComponent', () => {
+ function createMockProperty(name: string, required: boolean, description: string): ConnectorPropertyDescriptor {
+ return {
+ name,
+ description,
+ required,
+ allowableValuesFetchable: false
+ };
+ }
+
+ function createMockPropertyGroup(name: string, properties: ConnectorPropertyDescriptor[]): ConnectorPropertyGroup {
+ return {
+ name,
+ description: `Description for ${name}`,
+ properties
+ };
+ }
+
+ function createMockConfigurationStep(name: string, propertyGroups: ConnectorPropertyGroup[]): ConfigurationStep {
+ return {
+ name,
+ description: `Description for ${name}`,
+ documented: false,
+ propertyGroups
+ };
+ }
+
+ async function setup(stateOverrides?: Partial<ConnectorDefinitionState>) {
+ const initialState = {
+ [errorFeatureKey]: initialErrorState,
+ [currentUserFeatureKey]: initialCurrentUserState,
+ [documentationFeatureKey]: {
+ [connectorDefinitionFeatureKey]: {
+ ...initialConnectorDefinitionState,
+ ...stateOverrides
+ }
+ }
+ };
+
+ await TestBed.configureTestingModule({
+ imports: [ConnectorDefinitionComponent],
+ providers: [provideMockStore({ initialState })]
+ }).compileComponents();
+
+ const fixture = TestBed.createComponent(ConnectorDefinitionComponent);
+ const component = fixture.componentInstance;
+ const store = TestBed.inject(MockStore);
+
+ fixture.detectChanges();
+
+ return { component, fixture, store };
+ }
+
+ describe('Component initialization', () => {
+ it('should create', async () => {
+ const { component } = await setup();
+ expect(component).toBeTruthy();
+ });
+
+ it('should have null connectorDefinitionState initially', async () => {
+ const { component } = await setup();
+ expect(component.connectorDefinitionState).toBeDefined();
+ });
+ });
+
+ describe('Loading state logic', () => {
+ it('should return true for isInitialLoading when connectorDefinition and error are null', async () => {
+ const { component } = await setup();
+ const state: ConnectorDefinitionState = {
+ connectorDefinition: null,
+ error: null,
+ status: 'pending',
+ stepDocumentation: {}
+ };
+ expect(component.isInitialLoading(state)).toBe(true);
+ });
+
+ it('should return false for isInitialLoading when connectorDefinition is present', async () => {
+ const { component } = await setup();
+ const state: ConnectorDefinitionState = {
+ connectorDefinition: {
+ group: 'org.apache.nifi',
+ artifact: 'nifi-test-nar',
+ version: '1.0.0',
+ type: 'org.apache.nifi.TestConnector',
+ typeDescription: 'Test Connector',
+ buildInfo: {
+ revision: 'abc123',
+ version: '1.0.0'
+ },
+ additionalDetails: false
+ },
+ error: null,
+ status: 'success',
+ stepDocumentation: {}
+ };
+ expect(component.isInitialLoading(state)).toBe(false);
+ });
+
+ it('should return false for isInitialLoading when error is present', async () => {
+ const { component } = await setup();
+ const state: ConnectorDefinitionState = {
+ connectorDefinition: null,
+ error: 'Failed to load',
+ status: 'error',
+ stepDocumentation: {}
+ };
+ expect(component.isInitialLoading(state)).toBe(false);
+ });
+ });
+
+ describe('Configuration step helpers', () => {
+ it('should return true for hasConfigurationSteps when steps array has items', async () => {
+ const { component } = await setup();
+ const steps = [createMockConfigurationStep('Step 1', [])];
+ expect(component.hasConfigurationSteps(steps)).toBe(true);
+ });
+
+ it('should return false for hasConfigurationSteps when steps array is empty', async () => {
+ const { component } = await setup();
+ expect(component.hasConfigurationSteps([])).toBe(false);
+ });
+
+ it('should return false for hasConfigurationSteps when steps is undefined', async () => {
+ const { component } = await setup();
+ expect(component.hasConfigurationSteps(undefined)).toBe(false);
+ });
+ });
+
+ describe('Property group helpers', () => {
+ it('should return true for hasPropertyGroups when groups array has items', async () => {
+ const { component } = await setup();
+ const groups = [createMockPropertyGroup('Group 1', [])];
+ expect(component.hasPropertyGroups(groups)).toBe(true);
+ });
+
+ it('should return false for hasPropertyGroups when groups array is empty', async () => {
+ const { component } = await setup();
+ expect(component.hasPropertyGroups([])).toBe(false);
+ });
+
+ it('should return false for hasPropertyGroups when groups is undefined', async () => {
+ const { component } = await setup();
+ expect(component.hasPropertyGroups(undefined)).toBe(false);
+ });
+ });
+
+ describe('Property helpers', () => {
+ it('should return true for hasProperties when properties array has items', async () => {
+ const { component } = await setup();
+ const properties = [createMockProperty('prop1', true, 'Description')];
+ expect(component.hasProperties(properties)).toBe(true);
+ });
+
+ it('should return false for hasProperties when properties array is empty', async () => {
+ const { component } = await setup();
+ expect(component.hasProperties([])).toBe(false);
+ });
+
+ it('should return false for hasProperties when properties is undefined', async () => {
+ const { component } = await setup();
+ expect(component.hasProperties(undefined)).toBe(false);
+ });
+ });
+
+ describe('Property title formatting', () => {
+ it('should append asterisk for required properties', async () => {
+ const { component } = await setup();
+ const property = createMockProperty('Required Property', true, 'Description');
+ expect(component.formatPropertyTitle(property)).toBe('Required Property*');
+ });
+
+ it('should not append asterisk for optional properties', async () => {
+ const { component } = await setup();
+ const property = createMockProperty('Optional Property', false, 'Description');
+ expect(component.formatPropertyTitle(property)).toBe('Optional Property');
+ });
+ });
+
+ describe('Property lookup', () => {
+ it('should return a function that finds properties by name', async () => {
+ const { component } = await setup();
+ const properties = [
+ createMockProperty('prop1', true, 'First property'),
+ createMockProperty('prop2', false, 'Second property')
+ ];
+
+ const lookupFn = component.lookupProperty(properties);
+
+ expect(lookupFn('prop1')).toEqual(properties[0]);
+ expect(lookupFn('prop2')).toEqual(properties[1]);
+ expect(lookupFn('nonexistent')).toBeUndefined();
+ });
+ });
+
+ describe('Step documentation', () => {
+ it('should return false for isStepDocumentationLoading when step is not in state', async () => {
+ const { component } = await setup();
+ expect(component.isStepDocumentationLoading('Some Step')).toBe(false);
+ });
+
+ it('should return true for isStepDocumentationLoading when step status is loading', async () => {
+ const { component } = await setup({
+ stepDocumentation: {
+ 'Some Step': { documentation: null, error: null, status: 'loading' }
+ }
+ });
+ expect(component.isStepDocumentationLoading('Some Step')).toBe(true);
+ });
+
+ it('should return undefined for getStepDocumentation when step is not in state', async () => {
+ const { component } = await setup();
+ expect(component.getStepDocumentation('Some Step')).toBeUndefined();
+ });
+
+ it('should return documentation when step has loaded documentation', async () => {
+ const { component } = await setup({
+ stepDocumentation: {
+ 'Some Step': { documentation: '# Step Documentation', error: null, status: 'success' }
+ }
+ });
+ expect(component.getStepDocumentation('Some Step')).toBe('# Step Documentation');
+ });
+
+ it('should return undefined for getStepDocumentationError when step is not in state', async () => {
+ const { component } = await setup();
+ expect(component.getStepDocumentationError('Some Step')).toBeUndefined();
+ });
+
+ it('should return error when step has an error', async () => {
+ const { component } = await setup({
+ stepDocumentation: {
+ 'Some Step': { documentation: null, error: 'Failed to load', status: 'error' }
+ }
+ });
+ expect(component.getStepDocumentationError('Some Step')).toBe('Failed to load');
+ });
+ });
+
+ describe('onStepExpanded', () => {
+ function createMockConnectorDefinition(): ConnectorDefinition {
+ return {
+ group: 'org.apache.nifi',
+ artifact: 'nifi-test-nar',
+ version: '1.0.0',
+ type: 'org.apache.nifi.TestConnector',
+ typeDescription: 'Test Connector',
+ buildInfo: { revision: 'abc123', version: '1.0.0' },
+ additionalDetails: false
+ };
+ }
+
+ it('should call loadStepDocumentation when step is documented', async () => {
+ const { component, store } = await setup();
+ const dispatchSpy = jest.spyOn(store, 'dispatch');
+ const connectorDefinition = createMockConnectorDefinition();
+ const step = createMockConfigurationStep('Documented Step', []);
+ step.documented = true;
+
+ component.onStepExpanded(connectorDefinition, step);
+
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ loadStepDocumentation({
+ coordinates: {
+ group: connectorDefinition.group,
+ artifact: connectorDefinition.artifact,
+ version: connectorDefinition.version,
+ type: connectorDefinition.type
+ },
+ stepName: 'Documented Step'
+ })
+ );
+ });
+
+ it('should not call loadStepDocumentation when step is not documented', async () => {
+ const { component, store } = await setup();
+ const dispatchSpy = jest.spyOn(store, 'dispatch');
+ dispatchSpy.mockClear();
+ const connectorDefinition = createMockConnectorDefinition();
+ const step = createMockConfigurationStep('Undocumented Step', []);
+ step.documented = false;
+
+ component.onStepExpanded(connectorDefinition, step);
+
+ expect(dispatchSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('loadStepDocumentation deduplication', () => {
+ function createMockConnectorDefinition(): ConnectorDefinition {
+ return {
+ group: 'org.apache.nifi',
+ artifact: 'nifi-test-nar',
+ version: '1.0.0',
+ type: 'org.apache.nifi.TestConnector',
+ typeDescription: 'Test Connector',
+ buildInfo: { revision: 'abc123', version: '1.0.0' },
+ additionalDetails: false
+ };
+ }
+
+ it('should not dispatch when step is already loading', async () => {
+ const { component, store } = await setup({
+ stepDocumentation: {
+ 'Loading Step': { documentation: null, error: null, status: 'loading' }
+ }
+ });
+ const dispatchSpy = jest.spyOn(store, 'dispatch');
+ dispatchSpy.mockClear();
+ const connectorDefinition = createMockConnectorDefinition();
+
+ component.loadStepDocumentation(connectorDefinition, 'Loading Step');
+
+ expect(dispatchSpy).not.toHaveBeenCalled();
+ });
+
+ it('should not dispatch when step already has documentation', async () => {
+ const { component, store } = await setup({
+ stepDocumentation: {
+ 'Loaded Step': { documentation: '# Docs', error: null, status: 'success' }
+ }
+ });
+ const dispatchSpy = jest.spyOn(store, 'dispatch');
+ dispatchSpy.mockClear();
+ const connectorDefinition = createMockConnectorDefinition();
+
+ component.loadStepDocumentation(connectorDefinition, 'Loaded Step');
+
+ expect(dispatchSpy).not.toHaveBeenCalled();
+ });
+
+ it('should dispatch when step is not in state', async () => {
+ const { component, store } = await setup();
+ const dispatchSpy = jest.spyOn(store, 'dispatch');
+ dispatchSpy.mockClear();
+ const connectorDefinition = createMockConnectorDefinition();
+
+ component.loadStepDocumentation(connectorDefinition, 'New Step');
+
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ loadStepDocumentation({
+ coordinates: {
+ group: connectorDefinition.group,
+ artifact: connectorDefinition.artifact,
+ version: connectorDefinition.version,
+ type: connectorDefinition.type
+ },
+ stepName: 'New Step'
+ })
+ );
+ });
+
+ it('should dispatch when step had an error', async () => {
+ const { component, store } = await setup({
+ stepDocumentation: {
+ 'Error Step': { documentation: null, error: 'Failed', status: 'error' }
+ }
+ });
+ const dispatchSpy = jest.spyOn(store, 'dispatch');
+ dispatchSpy.mockClear();
+ const connectorDefinition = createMockConnectorDefinition();
+
+ component.loadStepDocumentation(connectorDefinition, 'Error Step');
+
+ expect(dispatchSpy).toHaveBeenCalledWith(
+ loadStepDocumentation({
+ coordinates: {
+ group: connectorDefinition.group,
+ artifact: connectorDefinition.artifact,
+ version: connectorDefinition.version,
+ type: connectorDefinition.type
+ },
+ stepName: 'Error Step'
+ })
+ );
+ });
+ });
+});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.ts
new file mode 100644
index 0000000000..7e57bb0dea
--- /dev/null
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/documentation/ui/connector-definition/connector-definition.component.ts
@@ -0,0 +1,204 @@
+/*
+ * 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 { Component, OnDestroy, ViewChild, inject } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../../../../state';
+import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
+import { ComponentType, isDefinedAndNotNull, NiFiCommon } from '@nifi/shared';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { selectDefinitionCoordinatesFromRouteForComponentType } from '../../state/documentation/documentation.selectors';
+import { distinctUntilChanged } from 'rxjs';
+import {
+ ConnectorDefinitionState,
+ ConfigurationStep,
+ ConnectorPropertyGroup,
+ ConnectorPropertyDescriptor,
+ ConnectorDefinition,
+ StepDocumentationState
+} from '../../state/connector-definition';
+import {
+ loadConnectorDefinition,
+ loadStepDocumentation,
+ resetConnectorDefinitionState
+} from '../../state/connector-definition/connector-definition.actions';
+import { selectConnectorDefinitionState } from '../../state/connector-definition/connector-definition.selectors';
+import { MatAccordion, MatExpansionModule } from '@angular/material/expansion';
+import { MatButtonModule } from '@angular/material/button';
+import { SeeAlsoComponent } from '../common/see-also/see-also.component';
+import { MarkdownComponent } from 'ngx-markdown';
+import { ConnectorPropertyDefinitionComponent } from '../common/connector-property-definition/connector-property-definition.component';
+
+@Component({
+ selector: 'connector-definition',
+ imports: [
+ NgxSkeletonLoaderModule,
+ MatExpansionModule,
+ MatButtonModule,
+ SeeAlsoComponent,
+ MarkdownComponent,
+ ConnectorPropertyDefinitionComponent
+ ],
+ templateUrl: './connector-definition.component.html',
+ styleUrl: './connector-definition.component.scss'
+})
+export class ConnectorDefinitionComponent implements OnDestroy {
+ private store = inject<Store<NiFiState>>(Store);
+ private nifiCommon = inject(NiFiCommon);
+
+ @ViewChild('stepsAccordion') stepsAccordion!: MatAccordion;
+
+ connectorDefinitionState: ConnectorDefinitionState | null = null;
+
+ constructor() {
+ this.store
+ .select(selectDefinitionCoordinatesFromRouteForComponentType(ComponentType.Connector))
+ .pipe(
+ isDefinedAndNotNull(),
+ distinctUntilChanged(
+ (a, b) =>
+ a.group === b.group && a.artifact === b.artifact && a.version === b.version && a.type === b.type
+ ),
+ takeUntilDestroyed()
+ )
+ .subscribe((coordinates) => {
+ this.store.dispatch(
+ loadConnectorDefinition({
+ coordinates
+ })
+ );
+ });
+
+ this.store
+ .select(selectConnectorDefinitionState)
+ .pipe(takeUntilDestroyed())
+ .subscribe((connectorDefinitionState) => {
+ const previousState = this.connectorDefinitionState;
+ this.connectorDefinitionState = connectorDefinitionState;
+
+ if (connectorDefinitionState.status === 'loading') {
+ window.scrollTo({ top: 0, left: 0 });
+ }
+
+ if (
+ previousState?.status !== 'success' &&
+ connectorDefinitionState.status === 'success' &&
+ connectorDefinitionState.connectorDefinition
+ ) {
+ const firstStep = connectorDefinitionState.connectorDefinition.configurationSteps?.[0];
+ if (firstStep?.documented) {
+ this.loadStepDocumentation(connectorDefinitionState.connectorDefinition, firstStep.name);
+ }
+ }
+ });
+ }
+
+ isInitialLoading(state: ConnectorDefinitionState): boolean {
+ return state.connectorDefinition === null && state.error === null;
+ }
+
+ formatExtensionName(type: string): string {
+ return this.nifiCommon.getComponentTypeLabel(type);
+ }
+
+ hasConfigurationSteps(steps: ConfigurationStep[] | undefined): boolean {
+ return steps !== undefined && steps.length > 0;
+ }
+
+ hasPropertyGroups(groups: ConnectorPropertyGroup[] | undefined): boolean {
+ return groups !== undefined && groups.length > 0;
+ }
+
+ hasProperties(properties: ConnectorPropertyDescriptor[] | undefined): boolean {
+ return properties !== undefined && properties.length > 0;
+ }
+
+ formatPropertyTitle(descriptor: ConnectorPropertyDescriptor): string {
+ if (descriptor.required) {
+ return `${descriptor.name}*`;
+ }
+ return descriptor.name;
+ }
+
+ lookupProperty(
+ properties: ConnectorPropertyDescriptor[]
+ ): (name: string) => ConnectorPropertyDescriptor | undefined {
+ return (name: string) => properties.find((prop) => prop.name === name);
+ }
+
+ expandAllSteps(): void {
+ this.stepsAccordion.openAll();
+ }
+
+ collapseAllSteps(): void {
+ this.stepsAccordion.closeAll();
+ }
+
+ expandAllProperties(accordion: MatAccordion): void {
+ accordion.openAll();
+ }
+
+ collapseAllProperties(accordion: MatAccordion): void {
+ accordion.closeAll();
+ }
+
+ onStepExpanded(connectorDefinition: ConnectorDefinition, step: ConfigurationStep): void {
+ if (step.documented) {
+ this.loadStepDocumentation(connectorDefinition, step.name);
+ }
+ }
+
+ loadStepDocumentation(connectorDefinition: ConnectorDefinition, stepName: string): void {
+ const stepState = this.getStepDocumentationState(stepName);
+ if (stepState && (stepState.status === 'loading' || stepState.status === 'success')) {
+ return;
+ }
+
+ this.store.dispatch(
+ loadStepDocumentation({
+ coordinates: {
+ group: connectorDefinition.group,
+ artifact: connectorDefinition.artifact,
+ version: connectorDefinition.version,
+ type: connectorDefinition.type
+ },
+ stepName
+ })
+ );
+ }
+
+ getStepDocumentationState(stepName: string): StepDocumentationState | undefined {
+ return this.connectorDefinitionState?.stepDocumentation[stepName];
+ }
+
+ isStepDocumentationLoading(stepName: string): boolean {
+ const state = this.getStepDocumentationState(stepName);
+ return state?.status === 'loading';
+ }
+
+ getStepDocumentation(stepName: string): string | undefined {
+ return this.getStepDocumentationState(stepName)?.documentation ?? undefined;
+ }
+
+ getStepDocumentationError(stepName: string): string | undefined {
+ return this.getStepDocumentationState(stepName)?.error ?? undefined;
+ }
+
+ ngOnDestroy(): void {
+ this.store.dispatch(resetConnectorDefinitionState());
+ }
+}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.spec.ts
index dca5347d2e..953caea168 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.spec.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.spec.ts
@@ -134,6 +134,10 @@ describe('FlowAnalysisRuleTable', () => {
canRead: true,
canWrite: true
},
+ connectorsPermissions: {
+ canRead: true,
+ canWrite: true
+ },
componentRestrictionPermissions: [],
canVersionFlows: true,
logoutSupported: true
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.spec.ts
index 55a5cf4db5..82f7012812 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.spec.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.spec.ts
@@ -127,6 +127,10 @@ describe('ManagementControllerServices', () => {
canRead: true,
canWrite: true
},
+ connectorsPermissions: {
+ canRead: true,
+ canWrite: true
+ },
restrictedComponentsPermissions: {
canRead: true,
canWrite: true
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.spec.ts
index 0133931981..76d51030a0 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.spec.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/parameter-providers/parameter-providers-table/parameter-providers-table.component.spec.ts
@@ -122,6 +122,10 @@ describe('ParameterProvidersTable', () => {
canRead: true,
canWrite: true
},
+ connectorsPermissions: {
+ canRead: true,
+ canWrite: true
+ },
restrictedComponentsPermissions: {
canRead: true,
canWrite: true
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.spec.ts
index 9008c53a15..cc79f9c228 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.spec.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.spec.ts
@@ -138,6 +138,10 @@ describe('ReportingTaskTable', () => {
controllerPermissions: {
canRead: true,
canWrite: true
+ },
+ connectorsPermissions: {
+ canRead: true,
+ canWrite: true
}
};
}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts
index c4258373bb..e69bf072f9 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/users/ui/user-listing/user-table/user-table.component.spec.ts
@@ -61,6 +61,10 @@ describe('UserTable', () => {
canRead: true,
canWrite: true
},
+ connectorsPermissions: {
+ canRead: false,
+ canWrite: false
+ },
restrictedComponentsPermissions: {
canRead: false,
canWrite: true
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/extension-types.service.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/extension-types.service.ts
index 477b1b6510..f766758425 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/extension-types.service.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/extension-types.service.ts
@@ -99,4 +99,8 @@ export class ExtensionTypesService {
getParameterProviderTypes(): Observable<any> {
return this.httpClient.get(`${ExtensionTypesService.API}/flow/parameter-provider-types`);
}
+
+ getConnectorTypes(): Observable<any> {
+ return this.httpClient.get(`${ExtensionTypesService.API}/flow/connector-types`);
+ }
}
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.effects.ts
index 256d15e192..98d411850a 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.effects.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.effects.ts
@@ -136,7 +136,8 @@ export class ExtensionTypesEffects {
this.extensionTypesService.getReportingTaskTypes(),
this.extensionTypesService.getRegistryClientTypes(),
this.extensionTypesService.getParameterProviderTypes(),
- this.extensionTypesService.getFlowAnalysisRuleTypes()
+ this.extensionTypesService.getFlowAnalysisRuleTypes(),
+ this.extensionTypesService.getConnectorTypes()
]).pipe(
map(
([
@@ -145,7 +146,8 @@ export class ExtensionTypesEffects {
reportingTaskTypes,
registryClientTypes,
parameterProviderTypes,
- flowAnalysisRuleTypes
+ flowAnalysisRuleTypes,
+ connectorTypes
]) =>
ExtensionTypesActions.loadExtensionTypesForDocumentationSuccess({
response: {
@@ -154,7 +156,8 @@ export class ExtensionTypesEffects {
reportingTaskTypes: reportingTaskTypes.reportingTaskTypes,
registryClientTypes: registryClientTypes.flowRegistryClientTypes,
parameterProviderTypes: parameterProviderTypes.parameterProviderTypes,
- flowAnalysisRuleTypes: flowAnalysisRuleTypes.flowAnalysisRuleTypes
+ flowAnalysisRuleTypes: flowAnalysisRuleTypes.flowAnalysisRuleTypes,
+ connectorTypes: connectorTypes.connectorTypes
}
})
),
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.reducer.ts
index a06944ca42..3ac1f8c918 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.reducer.ts
@@ -37,6 +37,7 @@ export const initialExtensionsTypesState: ExtensionTypesState = {
registryClientTypes: [],
flowAnalysisRuleTypes: [],
parameterProviderTypes: [],
+ connectorTypes: [],
status: 'pending'
};
@@ -56,6 +57,7 @@ export const extensionTypesReducer = createReducer(
registryClientTypes: [],
parameterProviderTypes: [],
flowAnalysisRuleTypes: [],
+ connectorTypes: [],
status: 'loading' as const
})
),
@@ -92,6 +94,7 @@ export const extensionTypesReducer = createReducer(
registryClientTypes: response.registryClientTypes,
parameterProviderTypes: response.parameterProviderTypes,
flowAnalysisRuleTypes: response.flowAnalysisRuleTypes,
+ connectorTypes: response.connectorTypes,
status: 'success' as const
})),
on(extensionTypesApiError, (state) => ({
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.selectors.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.selectors.ts
index 65cc3e4f57..059a4da62e 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.selectors.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/extension-types.selectors.ts
@@ -61,6 +61,11 @@ export const selectParameterProviderTypes = createSelector(
(state: ExtensionTypesState) => state.parameterProviderTypes
);
+export const selectConnectorTypes = createSelector(
+ selectExtensionTypesState,
+ (state: ExtensionTypesState) => state.connectorTypes
+);
+
export const selectTypesToIdentifyComponentRestrictions = createSelector(
selectExtensionTypesState,
(state: ExtensionTypesState) => {
@@ -139,6 +144,7 @@ export const selectExtensionFromTypes = (extensionTypes: string[]) =>
reportingTaskTypes: state.reportingTaskTypes.filter(typeFilter),
registryClientTypes: state.registryClientTypes.filter(typeFilter),
parameterProviderTypes: state.parameterProviderTypes.filter(typeFilter),
- flowAnalysisRuleTypes: state.flowAnalysisRuleTypes.filter(typeFilter)
+ flowAnalysisRuleTypes: state.flowAnalysisRuleTypes.filter(typeFilter),
+ connectorTypes: state.connectorTypes.filter(typeFilter)
} as LoadExtensionTypesForDocumentationResponse;
});
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/index.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/index.ts
index 05d2d351d0..249772cb94 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/index.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/extension-types/index.ts
@@ -50,6 +50,7 @@ export interface LoadExtensionTypesForDocumentationResponse {
registryClientTypes: DocumentedType[];
flowAnalysisRuleTypes: DocumentedType[];
parameterProviderTypes: DocumentedType[];
+ connectorTypes: DocumentedType[];
}
export interface ExtensionTypesState {
@@ -60,5 +61,6 @@ export interface ExtensionTypesState {
registryClientTypes: DocumentedType[];
flowAnalysisRuleTypes: DocumentedType[];
parameterProviderTypes: DocumentedType[];
+ connectorTypes: DocumentedType[];
status: ExtensionTypesLoadingStatus;
}
diff --git a/nifi-frontend/src/main/frontend/libs/shared/src/types/index.ts b/nifi-frontend/src/main/frontend/libs/shared/src/types/index.ts
index 5f1e21ab13..25e922995d 100644
--- a/nifi-frontend/src/main/frontend/libs/shared/src/types/index.ts
+++ b/nifi-frontend/src/main/frontend/libs/shared/src/types/index.ts
@@ -57,6 +57,7 @@ export enum ComponentType {
FlowAnalysisRule = 'FlowAnalysisRule',
ParameterProvider = 'ParameterProvider',
FlowRegistryClient = 'FlowRegistryClient',
+ Connector = 'Connector',
Flow = 'Flow'
}
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStep.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStep.java
new file mode 100644
index 0000000000..2c704d4e1c
--- /dev/null
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStep.java
@@ -0,0 +1,80 @@
+/*
+ * 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.extension.manifest;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlElementWrapper;
+import java.util.List;
+
+/**
+ * Represents a configuration step for a Connector.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ConfigurationStep {
+
+ private String name;
+ private String description;
+
+ @XmlElementWrapper
+ @XmlElement(name = "stepDependency")
+ private List<ConfigurationStepDependency> stepDependencies;
+
+ @XmlElementWrapper
+ @XmlElement(name = "propertyGroup")
+ private List<ConnectorPropertyGroup> propertyGroups;
+
+ @Schema(description = "The name of the configuration step")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "The description of the configuration step")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Schema(description = "The dependencies that this step has on other steps")
+ public List<ConfigurationStepDependency> getStepDependencies() {
+ return stepDependencies;
+ }
+
+ public void setStepDependencies(List<ConfigurationStepDependency> stepDependencies) {
+ this.stepDependencies = stepDependencies;
+ }
+
+ @Schema(description = "The property groups in this configuration step")
+ public List<ConnectorPropertyGroup> getPropertyGroups() {
+ return propertyGroups;
+ }
+
+ public void setPropertyGroups(List<ConnectorPropertyGroup> propertyGroups) {
+ this.propertyGroups = propertyGroups;
+ }
+}
+
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStepDependency.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStepDependency.java
new file mode 100644
index 0000000000..a702f57675
--- /dev/null
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStepDependency.java
@@ -0,0 +1,67 @@
+/*
+ * 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.extension.manifest;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlElementWrapper;
+import java.util.List;
+
+/**
+ * Represents a dependency that a configuration step has on another step's property.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ConfigurationStepDependency {
+
+ private String stepName;
+ private String propertyName;
+
+ @XmlElementWrapper
+ @XmlElement(name = "dependentValue")
+ private List<String> dependentValues;
+
+ @Schema(description = "The name of the step that this step depends on")
+ public String getStepName() {
+ return stepName;
+ }
+
+ public void setStepName(String stepName) {
+ this.stepName = stepName;
+ }
+
+ @Schema(description = "The name of the property within the step that this step depends on")
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ public void setPropertyName(String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ @Schema(description = "The values of the dependent property that enable this step")
+ public List<String> getDependentValues() {
+ return dependentValues;
+ }
+
+ public void setDependentValues(List<String> dependentValues) {
+ this.dependentValues = dependentValues;
+ }
+}
+
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorProperty.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorProperty.java
new file mode 100644
index 0000000000..f006772070
--- /dev/null
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorProperty.java
@@ -0,0 +1,120 @@
+/*
+ * 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.extension.manifest;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlElementWrapper;
+import java.util.List;
+
+/**
+ * Represents a property descriptor for a Connector.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ConnectorProperty {
+
+ private String name;
+ private String description;
+ private String defaultValue;
+ private boolean required;
+ private ConnectorPropertyType propertyType;
+ private boolean allowableValuesFetchable;
+
+ @XmlElementWrapper
+ @XmlElement(name = "allowableValue")
+ private List<AllowableValue> allowableValues;
+
+ @XmlElementWrapper
+ @XmlElement(name = "dependency")
+ private List<ConnectorPropertyDependency> dependencies;
+
+ @Schema(description = "The name of the property")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "The description of the property")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Schema(description = "The default value of the property")
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public void setDefaultValue(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Schema(description = "Whether or not the property is required")
+ public boolean isRequired() {
+ return required;
+ }
+
+ public void setRequired(boolean required) {
+ this.required = required;
+ }
+
+ @Schema(description = "The type of the property")
+ public ConnectorPropertyType getPropertyType() {
+ return propertyType;
+ }
+
+ public void setPropertyType(ConnectorPropertyType propertyType) {
+ this.propertyType = propertyType;
+ }
+
+ @Schema(description = "Whether or not the allowable values can be fetched dynamically")
+ public boolean isAllowableValuesFetchable() {
+ return allowableValuesFetchable;
+ }
+
+ public void setAllowableValuesFetchable(boolean allowableValuesFetchable) {
+ this.allowableValuesFetchable = allowableValuesFetchable;
+ }
+
+ @Schema(description = "The allowable values for this property")
+ public List<AllowableValue> getAllowableValues() {
+ return allowableValues;
+ }
+
+ public void setAllowableValues(List<AllowableValue> allowableValues) {
+ this.allowableValues = allowableValues;
+ }
+
+ @Schema(description = "The properties that this property depends on")
+ public List<ConnectorPropertyDependency> getDependencies() {
+ return dependencies;
+ }
+
+ public void setDependencies(List<ConnectorPropertyDependency> dependencies) {
+ this.dependencies = dependencies;
+ }
+}
+
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyDependency.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyDependency.java
new file mode 100644
index 0000000000..5d649e9475
--- /dev/null
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyDependency.java
@@ -0,0 +1,57 @@
+/*
+ * 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.extension.manifest;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlElementWrapper;
+import java.util.List;
+
+/**
+ * Represents a dependency that a connector property has on another property.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ConnectorPropertyDependency {
+
+ private String propertyName;
+
+ @XmlElementWrapper
+ @XmlElement(name = "dependentValue")
+ private List<String> dependentValues;
+
+ @Schema(description = "The name of the property that this property depends on")
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ public void setPropertyName(String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ @Schema(description = "The values of the dependent property that enable this property")
+ public List<String> getDependentValues() {
+ return dependentValues;
+ }
+
+ public void setDependentValues(List<String> dependentValues) {
+ this.dependentValues = dependentValues;
+ }
+}
+
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyGroup.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyGroup.java
new file mode 100644
index 0000000000..7d01827432
--- /dev/null
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyGroup.java
@@ -0,0 +1,67 @@
+/*
+ * 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.extension.manifest;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlElementWrapper;
+import java.util.List;
+
+/**
+ * Represents a group of properties within a configuration step.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ConnectorPropertyGroup {
+
+ private String name;
+ private String description;
+
+ @XmlElementWrapper
+ @XmlElement(name = "property")
+ private List<ConnectorProperty> properties;
+
+ @Schema(description = "The name of the property group")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "The description of the property group")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Schema(description = "The properties in this group")
+ public List<ConnectorProperty> getProperties() {
+ return properties;
+ }
+
+ public void setProperties(List<ConnectorProperty> properties) {
+ this.properties = properties;
+ }
+}
+
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyType.java
similarity index 81%
copy from nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
copy to nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyType.java
index 0413e260ed..07ced332c7 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyType.java
@@ -17,20 +17,27 @@
package org.apache.nifi.extension.manifest;
/**
- * Possible types of extensions.
+ * Possible types for connector properties.
*/
-public enum ExtensionType {
+public enum ConnectorPropertyType {
- PROCESSOR,
+ STRING,
- CONTROLLER_SERVICE,
+ INTEGER,
- REPORTING_TASK,
+ BOOLEAN,
- FLOW_ANALYSIS_RULE,
+ FLOAT,
- PARAMETER_PROVIDER,
+ DOUBLE,
- FLOW_REGISTRY_CLIENT;
+ STRING_LIST,
+
+ SECRET,
+
+ ASSET,
+
+ ASSET_LIST;
}
+
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/Extension.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/Extension.java
index 1d06d35516..0d5dc24384 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/Extension.java
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/Extension.java
@@ -107,6 +107,11 @@ public class Extension {
@XmlElement(name = "multiProcessorUseCase")
private List<MultiProcessorUseCase> multiProcessorUseCases;
+ // Connector-specific fields
+ @XmlElementWrapper
+ @XmlElement(name = "configurationStep")
+ private List<ConfigurationStep> configurationSteps;
+
@Schema(description = "The name of the extension")
public String getName() {
return name;
@@ -358,6 +363,15 @@ public class Extension {
this.multiProcessorUseCases = multiProcessorUseCases;
}
+ @Schema(description = "The configuration steps for a Connector extension")
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return configurationSteps;
+ }
+
+ public void setConfigurationSteps(final List<ConfigurationStep> configurationSteps) {
+ this.configurationSteps = configurationSteps;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
index 0413e260ed..a72511dabd 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ExtensionType.java
@@ -31,6 +31,8 @@ public enum ExtensionType {
PARAMETER_PROVIDER,
- FLOW_REGISTRY_CLIENT;
+ FLOW_REGISTRY_CLIENT,
+
+ CONNECTOR;
}
diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ComponentManifestBuilder.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ComponentManifestBuilder.java
index 9eb3f697ff..4ced855141 100644
--- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ComponentManifestBuilder.java
+++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/ComponentManifestBuilder.java
@@ -17,6 +17,7 @@
package org.apache.nifi.runtime.manifest;
import org.apache.nifi.c2.protocol.component.api.ComponentManifest;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowAnalysisRuleDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowRegistryClientDefinition;
@@ -65,6 +66,12 @@ public interface ComponentManifestBuilder {
*/
ComponentManifestBuilder addFlowRegistryClient(FlowRegistryClientDefinition flowRegistryClientDefinition);
+ /**
+ * @param connectorDefinition a connector definition to add
+ * @return the builder
+ */
+ ComponentManifestBuilder addConnector(ConnectorDefinition connectorDefinition);
+
/**
* @return a component manifest containing all the added definitions
*/
diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardComponentManifestBuilder.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardComponentManifestBuilder.java
index b6b67181bc..712aab2187 100644
--- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardComponentManifestBuilder.java
+++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardComponentManifestBuilder.java
@@ -17,6 +17,7 @@
package org.apache.nifi.runtime.manifest.impl;
import org.apache.nifi.c2.protocol.component.api.ComponentManifest;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowAnalysisRuleDefinition;
import org.apache.nifi.c2.protocol.component.api.FlowRegistryClientDefinition;
@@ -39,6 +40,7 @@ public class StandardComponentManifestBuilder implements ComponentManifestBuilde
private final List<ParameterProviderDefinition> parameterProviders = new ArrayList<>();
private final List<FlowAnalysisRuleDefinition> flowAnalysisRules = new ArrayList<>();
private final List<FlowRegistryClientDefinition> flowRegistryClients = new ArrayList<>();
+ private final List<ConnectorDefinition> connectors = new ArrayList<>();
@Override
public ComponentManifestBuilder addProcessor(final ProcessorDefinition processorDefinition) {
@@ -94,6 +96,15 @@ public class StandardComponentManifestBuilder implements ComponentManifestBuilde
return this;
}
+ @Override
+ public ComponentManifestBuilder addConnector(final ConnectorDefinition connectorDefinition) {
+ if (connectorDefinition == null) {
+ throw new IllegalArgumentException("Connector definition cannot be null");
+ }
+ connectors.add(connectorDefinition);
+ return this;
+ }
+
@Override
public ComponentManifest build() {
final ComponentManifest componentManifest = new ComponentManifest();
@@ -103,6 +114,7 @@ public class StandardComponentManifestBuilder implements ComponentManifestBuilde
componentManifest.setParameterProviders(new ArrayList<>(parameterProviders));
componentManifest.setFlowAnalysisRules(new ArrayList<>(flowAnalysisRules));
componentManifest.setFlowRegistryClients(new ArrayList<>(flowRegistryClients));
+ componentManifest.setConnectors(new ArrayList<>(connectors));
return componentManifest;
}
diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
index a080378e5e..b36add8336 100644
--- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
+++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
@@ -20,6 +20,13 @@ import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.c2.protocol.component.api.BuildInfo;
import org.apache.nifi.c2.protocol.component.api.Bundle;
import org.apache.nifi.c2.protocol.component.api.ConfigurableComponentDefinition;
+import org.apache.nifi.c2.protocol.component.api.ConfigurationStep;
+import org.apache.nifi.c2.protocol.component.api.ConfigurationStepDependency;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
+import org.apache.nifi.c2.protocol.component.api.ConnectorPropertyDependency;
+import org.apache.nifi.c2.protocol.component.api.ConnectorPropertyDescriptor;
+import org.apache.nifi.c2.protocol.component.api.ConnectorPropertyGroup;
+import org.apache.nifi.c2.protocol.component.api.ConnectorPropertyType;
import org.apache.nifi.c2.protocol.component.api.ControllerServiceDefinition;
import org.apache.nifi.c2.protocol.component.api.DefinedType;
import org.apache.nifi.c2.protocol.component.api.ExtensionComponent;
@@ -63,6 +70,7 @@ import org.apache.nifi.extension.manifest.ResourceDefinition;
import org.apache.nifi.extension.manifest.Restricted;
import org.apache.nifi.extension.manifest.Stateful;
import org.apache.nifi.extension.manifest.SystemResourceConsideration;
+import org.apache.nifi.extension.manifest.ConnectorProperty;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.runtime.manifest.ComponentManifestBuilder;
import org.apache.nifi.runtime.manifest.ExtensionManifestContainer;
@@ -226,6 +234,9 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
case FLOW_REGISTRY_CLIENT:
addFlowRegistryClientDefinition(extensionManifest, extension, additionalDetails, componentManifestBuilder);
break;
+ case CONNECTOR:
+ addConnectorDefinition(extensionManifest, extension, additionalDetails, componentManifestBuilder);
+ break;
}
}
@@ -410,6 +421,121 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
componentManifestBuilder.addFlowRegistryClient(flowRegistryClientDefinition);
}
+ private void addConnectorDefinition(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails,
+ final ComponentManifestBuilder componentManifestBuilder) {
+ final ConnectorDefinition connectorDefinition = new ConnectorDefinition();
+ populateDefinedType(extensionManifest, extension, connectorDefinition);
+ populateExtensionComponent(extensionManifest, extension, additionalDetails, connectorDefinition);
+
+ // Populate configuration steps
+ final List<org.apache.nifi.extension.manifest.ConfigurationStep> manifestSteps = extension.getConfigurationSteps();
+ if (isNotEmpty(manifestSteps)) {
+ connectorDefinition.setConfigurationSteps(
+ manifestSteps.stream()
+ .map(this::getConfigurationStep)
+ .collect(Collectors.toList())
+ );
+ }
+
+ componentManifestBuilder.addConnector(connectorDefinition);
+ }
+
+ private ConfigurationStep getConfigurationStep(final org.apache.nifi.extension.manifest.ConfigurationStep manifestStep) {
+ final ConfigurationStep step = new ConfigurationStep();
+ step.setName(manifestStep.getName());
+ step.setDescription(manifestStep.getDescription());
+
+ // Convert step dependencies
+ final List<org.apache.nifi.extension.manifest.ConfigurationStepDependency> manifestDeps = manifestStep.getStepDependencies();
+ if (isNotEmpty(manifestDeps)) {
+ step.setStepDependencies(
+ manifestDeps.stream()
+ .map(this::getConfigurationStepDependency)
+ .collect(Collectors.toList())
+ );
+ }
+
+ // Convert property groups
+ final List<org.apache.nifi.extension.manifest.ConnectorPropertyGroup> manifestGroups = manifestStep.getPropertyGroups();
+ if (isNotEmpty(manifestGroups)) {
+ step.setPropertyGroups(
+ manifestGroups.stream()
+ .map(this::getConnectorPropertyGroup)
+ .collect(Collectors.toList())
+ );
+ }
+
+ return step;
+ }
+
+ private ConfigurationStepDependency getConfigurationStepDependency(
+ final org.apache.nifi.extension.manifest.ConfigurationStepDependency manifestDep) {
+ final ConfigurationStepDependency dep = new ConfigurationStepDependency();
+ dep.setStepName(manifestDep.getStepName());
+ dep.setPropertyName(manifestDep.getPropertyName());
+ dep.setDependentValues(manifestDep.getDependentValues());
+ return dep;
+ }
+
+ private ConnectorPropertyGroup getConnectorPropertyGroup(
+ final org.apache.nifi.extension.manifest.ConnectorPropertyGroup manifestGroup) {
+ final ConnectorPropertyGroup group = new ConnectorPropertyGroup();
+ group.setName(manifestGroup.getName());
+ group.setDescription(manifestGroup.getDescription());
+
+ final List<ConnectorProperty> manifestProps = manifestGroup.getProperties();
+ if (isNotEmpty(manifestProps)) {
+ group.setProperties(
+ manifestProps.stream()
+ .map(this::getConnectorPropertyDescriptor)
+ .collect(Collectors.toList())
+ );
+ }
+
+ return group;
+ }
+
+ private ConnectorPropertyDescriptor getConnectorPropertyDescriptor(final ConnectorProperty manifestProp) {
+ final ConnectorPropertyDescriptor prop = new ConnectorPropertyDescriptor();
+ prop.setName(manifestProp.getName());
+ prop.setDescription(manifestProp.getDescription());
+ prop.setDefaultValue(manifestProp.getDefaultValue());
+ prop.setRequired(manifestProp.isRequired());
+ prop.setAllowableValuesFetchable(manifestProp.isAllowableValuesFetchable());
+
+ // Convert property type
+ final org.apache.nifi.extension.manifest.ConnectorPropertyType manifestType = manifestProp.getPropertyType();
+ if (manifestType != null) {
+ prop.setPropertyType(ConnectorPropertyType.valueOf(manifestType.name()));
+ }
+
+ // Convert allowable values
+ final List<AllowableValue> manifestValues = manifestProp.getAllowableValues();
+ if (isNotEmpty(manifestValues)) {
+ prop.setAllowableValues(getPropertyAllowableValues(manifestValues));
+ }
+
+ // Convert property dependencies
+ final List<org.apache.nifi.extension.manifest.ConnectorPropertyDependency> manifestDeps = manifestProp.getDependencies();
+ if (isNotEmpty(manifestDeps)) {
+ prop.setDependencies(
+ manifestDeps.stream()
+ .map(this::getConnectorPropertyDependency)
+ .collect(Collectors.toList())
+ );
+ }
+
+ return prop;
+ }
+
+ private ConnectorPropertyDependency getConnectorPropertyDependency(
+ final org.apache.nifi.extension.manifest.ConnectorPropertyDependency manifestDep) {
+ final ConnectorPropertyDependency dep = new ConnectorPropertyDependency();
+ dep.setPropertyName(manifestDep.getPropertyName());
+ dep.setDependentValues(manifestDep.getDependentValues());
+ return dep;
+ }
+
private void addFlowAnalysisRuleDefinition(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails,
final ComponentManifestBuilder componentManifestBuilder) {
final FlowAnalysisRuleDefinition flowAnalysisRuleDefinition = new FlowAnalysisRuleDefinition();
diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/test/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilderConnectorTest.java b/nifi-manifest/nifi-runtime-manifest-core/src/test/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilderConnectorTest.java
new file mode 100644
index 0000000000..8585dc4795
--- /dev/null
+++ b/nifi-manifest/nifi-runtime-manifest-core/src/test/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilderConnectorTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.runtime.manifest.impl;
+
+import org.apache.nifi.c2.protocol.component.api.ConfigurationStep;
+import org.apache.nifi.c2.protocol.component.api.ConfigurationStepDependency;
+import org.apache.nifi.c2.protocol.component.api.ConnectorDefinition;
+import org.apache.nifi.c2.protocol.component.api.ConnectorPropertyDescriptor;
+import org.apache.nifi.c2.protocol.component.api.ConnectorPropertyGroup;
+import org.apache.nifi.c2.protocol.component.api.ConnectorPropertyType;
+import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
+import org.apache.nifi.extension.manifest.AllowableValue;
+import org.apache.nifi.extension.manifest.ConnectorProperty;
+import org.apache.nifi.extension.manifest.ConnectorPropertyDependency;
+import org.apache.nifi.extension.manifest.Extension;
+import org.apache.nifi.extension.manifest.ExtensionManifest;
+import org.apache.nifi.extension.manifest.ExtensionType;
+import org.apache.nifi.extension.manifest.ParentNar;
+import org.apache.nifi.runtime.manifest.ExtensionManifestContainer;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class StandardRuntimeManifestBuilderConnectorTest {
+
+ private static final String TEST_CONNECTOR_TYPE = "org.apache.nifi.connectors.TestConnector";
+ private static final String TEST_GROUP = "org.apache.nifi";
+ private static final String TEST_ARTIFACT = "nifi-test-nar";
+ private static final String TEST_VERSION = "2.0.0";
+ private static final String STEP_NAME = "Connection Settings";
+ private static final String STEP_DESCRIPTION = "Configure connection parameters";
+ private static final String GROUP_NAME = "Authentication";
+ private static final String GROUP_DESCRIPTION = "Authentication settings";
+ private static final String PROPERTY_NAME = "username";
+ private static final String PROPERTY_DESCRIPTION = "The username for authentication";
+ private static final String PROPERTY_DEFAULT = "admin";
+
+ @Test
+ void testConnectorDefinitionIsBuiltFromExtensionManifest() {
+ final ExtensionManifest extensionManifest = createExtensionManifest();
+ final Extension connectorExtension = createConnectorExtension();
+ extensionManifest.setExtensions(List.of(connectorExtension));
+
+ final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, null);
+ final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder()
+ .addBundle(container)
+ .build();
+
+ assertNotNull(runtimeManifest);
+ assertNotNull(runtimeManifest.getBundles());
+ assertEquals(1, runtimeManifest.getBundles().size());
+
+ final List<ConnectorDefinition> connectors = runtimeManifest.getBundles().get(0).getComponentManifest().getConnectors();
+ assertNotNull(connectors);
+ assertEquals(1, connectors.size());
+
+ final ConnectorDefinition connector = connectors.get(0);
+ assertEquals(TEST_CONNECTOR_TYPE, connector.getType());
+ }
+
+ @Test
+ void testConfigurationStepsAreConverted() {
+ final ExtensionManifest extensionManifest = createExtensionManifest();
+ final Extension connectorExtension = createConnectorExtensionWithConfigurationSteps();
+ extensionManifest.setExtensions(List.of(connectorExtension));
+
+ final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, null);
+ final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder()
+ .addBundle(container)
+ .build();
+
+ final ConnectorDefinition connector = runtimeManifest.getBundles().get(0).getComponentManifest().getConnectors().get(0);
+ assertNotNull(connector.getConfigurationSteps());
+ assertEquals(1, connector.getConfigurationSteps().size());
+
+ final ConfigurationStep step = connector.getConfigurationSteps().get(0);
+ assertEquals(STEP_NAME, step.getName());
+ assertEquals(STEP_DESCRIPTION, step.getDescription());
+ }
+
+ @Test
+ void testConfigurationStepDependenciesAreConverted() {
+ final ExtensionManifest extensionManifest = createExtensionManifest();
+ final Extension connectorExtension = createConnectorExtensionWithStepDependencies();
+ extensionManifest.setExtensions(List.of(connectorExtension));
+
+ final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, null);
+ final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder()
+ .addBundle(container)
+ .build();
+
+ final ConfigurationStep step = runtimeManifest.getBundles().get(0).getComponentManifest().getConnectors().get(0).getConfigurationSteps().get(0);
+ assertNotNull(step.getStepDependencies());
+ assertEquals(1, step.getStepDependencies().size());
+
+ final ConfigurationStepDependency dependency = step.getStepDependencies().get(0);
+ assertEquals("previousStep", dependency.getStepName());
+ assertEquals("connectionType", dependency.getPropertyName());
+ assertEquals(List.of("advanced"), dependency.getDependentValues());
+ }
+
+ @Test
+ void testPropertyGroupsAreConverted() {
+ final ExtensionManifest extensionManifest = createExtensionManifest();
+ final Extension connectorExtension = createConnectorExtensionWithPropertyGroups();
+ extensionManifest.setExtensions(List.of(connectorExtension));
+
+ final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, null);
+ final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder()
+ .addBundle(container)
+ .build();
+
+ final ConfigurationStep step = runtimeManifest.getBundles().get(0).getComponentManifest().getConnectors().get(0).getConfigurationSteps().get(0);
+ assertNotNull(step.getPropertyGroups());
+ assertEquals(1, step.getPropertyGroups().size());
+
+ final ConnectorPropertyGroup group = step.getPropertyGroups().get(0);
+ assertEquals(GROUP_NAME, group.getName());
+ assertEquals(GROUP_DESCRIPTION, group.getDescription());
+ }
+
+ @Test
+ void testConnectorPropertiesAreConverted() {
+ final ExtensionManifest extensionManifest = createExtensionManifest();
+ final Extension connectorExtension = createConnectorExtensionWithProperties();
+ extensionManifest.setExtensions(List.of(connectorExtension));
+
+ final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, null);
+ final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder()
+ .addBundle(container)
+ .build();
+
+ final ConnectorPropertyGroup group = runtimeManifest.getBundles().get(0).getComponentManifest().getConnectors().get(0)
+ .getConfigurationSteps().get(0).getPropertyGroups().get(0);
+ assertNotNull(group.getProperties());
+ assertEquals(1, group.getProperties().size());
+
+ final ConnectorPropertyDescriptor property = group.getProperties().get(0);
+ assertEquals(PROPERTY_NAME, property.getName());
+ assertEquals(PROPERTY_DESCRIPTION, property.getDescription());
+ assertEquals(PROPERTY_DEFAULT, property.getDefaultValue());
+ assertTrue(property.isRequired());
+ assertFalse(property.isAllowableValuesFetchable());
+ assertEquals(ConnectorPropertyType.STRING, property.getPropertyType());
+ }
+
+ @Test
+ void testConnectorPropertyAllowableValuesAreConverted() {
+ final ExtensionManifest extensionManifest = createExtensionManifest();
+ final Extension connectorExtension = createConnectorExtensionWithAllowableValues();
+ extensionManifest.setExtensions(List.of(connectorExtension));
+
+ final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, null);
+ final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder()
+ .addBundle(container)
+ .build();
+
+ final ConnectorPropertyDescriptor property = runtimeManifest.getBundles().get(0).getComponentManifest().getConnectors().get(0)
+ .getConfigurationSteps().get(0).getPropertyGroups().get(0).getProperties().get(0);
+ assertNotNull(property.getAllowableValues());
+ assertEquals(2, property.getAllowableValues().size());
+ assertEquals("option1", property.getAllowableValues().get(0).getValue());
+ assertEquals("Option 1", property.getAllowableValues().get(0).getDisplayName());
+ }
+
+ @Test
+ void testConnectorPropertyDependenciesAreConverted() {
+ final ExtensionManifest extensionManifest = createExtensionManifest();
+ final Extension connectorExtension = createConnectorExtensionWithPropertyDependencies();
+ extensionManifest.setExtensions(List.of(connectorExtension));
+
+ final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, null);
+ final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder()
+ .addBundle(container)
+ .build();
+
+ final ConnectorPropertyDescriptor property = runtimeManifest.getBundles().get(0).getComponentManifest().getConnectors().get(0)
+ .getConfigurationSteps().get(0).getPropertyGroups().get(0).getProperties().get(0);
+ assertNotNull(property.getDependencies());
+ assertEquals(1, property.getDependencies().size());
+ assertEquals("authType", property.getDependencies().get(0).getPropertyName());
+ assertEquals(List.of("basic", "oauth"), property.getDependencies().get(0).getDependentValues());
+ }
+
+ private ExtensionManifest createExtensionManifest() {
+ final ExtensionManifest manifest = new ExtensionManifest();
+ manifest.setGroupId(TEST_GROUP);
+ manifest.setArtifactId(TEST_ARTIFACT);
+ manifest.setVersion(TEST_VERSION);
+
+ final ParentNar parentNar = new ParentNar();
+ parentNar.setGroupId("org.apache.nifi");
+ parentNar.setArtifactId("nifi-standard-services-api-nar");
+ parentNar.setVersion(TEST_VERSION);
+ manifest.setParentNar(parentNar);
+
+ return manifest;
+ }
+
+ private Extension createConnectorExtension() {
+ final Extension extension = new Extension();
+ extension.setName(TEST_CONNECTOR_TYPE);
+ extension.setType(ExtensionType.CONNECTOR);
+ return extension;
+ }
+
+ private Extension createConnectorExtensionWithConfigurationSteps() {
+ final Extension extension = createConnectorExtension();
+
+ final org.apache.nifi.extension.manifest.ConfigurationStep step = new org.apache.nifi.extension.manifest.ConfigurationStep();
+ step.setName(STEP_NAME);
+ step.setDescription(STEP_DESCRIPTION);
+ extension.setConfigurationSteps(List.of(step));
+
+ return extension;
+ }
+
+ private Extension createConnectorExtensionWithStepDependencies() {
+ final Extension extension = createConnectorExtension();
+
+ final org.apache.nifi.extension.manifest.ConfigurationStepDependency dependency = new org.apache.nifi.extension.manifest.ConfigurationStepDependency();
+ dependency.setStepName("previousStep");
+ dependency.setPropertyName("connectionType");
+ dependency.setDependentValues(List.of("advanced"));
+
+ final org.apache.nifi.extension.manifest.ConfigurationStep step = new org.apache.nifi.extension.manifest.ConfigurationStep();
+ step.setName(STEP_NAME);
+ step.setDescription(STEP_DESCRIPTION);
+ step.setStepDependencies(List.of(dependency));
+ extension.setConfigurationSteps(List.of(step));
+
+ return extension;
+ }
+
+ private Extension createConnectorExtensionWithPropertyGroups() {
+ final Extension extension = createConnectorExtension();
+
+ final org.apache.nifi.extension.manifest.ConnectorPropertyGroup group = new org.apache.nifi.extension.manifest.ConnectorPropertyGroup();
+ group.setName(GROUP_NAME);
+ group.setDescription(GROUP_DESCRIPTION);
+
+ final org.apache.nifi.extension.manifest.ConfigurationStep step = new org.apache.nifi.extension.manifest.ConfigurationStep();
+ step.setName(STEP_NAME);
+ step.setDescription(STEP_DESCRIPTION);
+ step.setPropertyGroups(List.of(group));
+ extension.setConfigurationSteps(List.of(step));
+
+ return extension;
+ }
+
+ private Extension createConnectorExtensionWithProperties() {
+ final Extension extension = createConnectorExtension();
+
+ final ConnectorProperty property = new ConnectorProperty();
+ property.setName(PROPERTY_NAME);
+ property.setDescription(PROPERTY_DESCRIPTION);
+ property.setDefaultValue(PROPERTY_DEFAULT);
+ property.setRequired(true);
+ property.setAllowableValuesFetchable(false);
+ property.setPropertyType(org.apache.nifi.extension.manifest.ConnectorPropertyType.STRING);
+
+ final org.apache.nifi.extension.manifest.ConnectorPropertyGroup group = new org.apache.nifi.extension.manifest.ConnectorPropertyGroup();
+ group.setName(GROUP_NAME);
+ group.setDescription(GROUP_DESCRIPTION);
+ group.setProperties(List.of(property));
+
+ final org.apache.nifi.extension.manifest.ConfigurationStep step = new org.apache.nifi.extension.manifest.ConfigurationStep();
+ step.setName(STEP_NAME);
+ step.setDescription(STEP_DESCRIPTION);
+ step.setPropertyGroups(List.of(group));
+ extension.setConfigurationSteps(List.of(step));
+
+ return extension;
+ }
+
+ private Extension createConnectorExtensionWithAllowableValues() {
+ final Extension extension = createConnectorExtension();
+
+ final AllowableValue value1 = new AllowableValue();
+ value1.setValue("option1");
+ value1.setDisplayName("Option 1");
+ value1.setDescription("First option");
+
+ final AllowableValue value2 = new AllowableValue();
+ value2.setValue("option2");
+ value2.setDisplayName("Option 2");
+ value2.setDescription("Second option");
+
+ final ConnectorProperty property = new ConnectorProperty();
+ property.setName(PROPERTY_NAME);
+ property.setDescription(PROPERTY_DESCRIPTION);
+ property.setAllowableValues(List.of(value1, value2));
+
+ final org.apache.nifi.extension.manifest.ConnectorPropertyGroup group = new org.apache.nifi.extension.manifest.ConnectorPropertyGroup();
+ group.setName(GROUP_NAME);
+ group.setProperties(List.of(property));
+
+ final org.apache.nifi.extension.manifest.ConfigurationStep step = new org.apache.nifi.extension.manifest.ConfigurationStep();
+ step.setName(STEP_NAME);
+ step.setPropertyGroups(List.of(group));
+ extension.setConfigurationSteps(List.of(step));
+
+ return extension;
+ }
+
+ private Extension createConnectorExtensionWithPropertyDependencies() {
+ final Extension extension = createConnectorExtension();
+
+ final ConnectorPropertyDependency dependency = new ConnectorPropertyDependency();
+ dependency.setPropertyName("authType");
+ dependency.setDependentValues(List.of("basic", "oauth"));
+
+ final ConnectorProperty property = new ConnectorProperty();
+ property.setName(PROPERTY_NAME);
+ property.setDescription(PROPERTY_DESCRIPTION);
+ property.setDependencies(List.of(dependency));
+
+ final org.apache.nifi.extension.manifest.ConnectorPropertyGroup group = new org.apache.nifi.extension.manifest.ConnectorPropertyGroup();
+ group.setName(GROUP_NAME);
+ group.setProperties(List.of(property));
+
+ final org.apache.nifi.extension.manifest.ConfigurationStep step = new org.apache.nifi.extension.manifest.ConfigurationStep();
+ step.setName(STEP_NAME);
+ step.setPropertyGroups(List.of(group));
+ extension.setConfigurationSteps(List.of(step));
+
+ return extension;
+ }
+}
+
diff --git a/pom.xml b/pom.xml
index 17d75c827e..62a49ceb62 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,7 +119,7 @@
<!-- NiFi build -->
<nifi-api.version>2.6.0-SNAPSHOT</nifi-api.version>
- <nifi.nar.maven.plugin.version>2.2.0</nifi.nar.maven.plugin.version>
+ <nifi.nar.maven.plugin.version>2.3.0-SNAPSHOT</nifi.nar.maven.plugin.version>
<!-- CSPs SDK -->
<software.amazon.awssdk.version>2.41.10</software.amazon.awssdk.version>
(nifi) 31/40: NIFI-15446: When invoking ConnectorMethod, make sure to serialize/des… (#10750)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 58735ea30efed9da4b9d0b156aabe156ecff7ca3
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Thu Jan 15 11:09:17 2026 -0500
NIFI-15446: When invoking ConnectorMethod, make sure to serialize/des… (#10750)
* NIFI-15446: When invoking ConnectorMethod, make sure to serialize/deserialize the arguments and return value
* NIFI-15446: Addressed review feedback
---
.../nifi/controller/StandardProcessorNode.java | 40 +++-
.../service/StandardControllerServiceNode.java | 41 +++-
.../org/apache/nifi/controller/ProcessorNode.java | 2 +-
.../controller/service/ControllerServiceNode.java | 2 +-
.../StandaloneControllerServiceFacade.java | 50 +++-
.../standalone/StandaloneProcessorFacade.java | 52 ++++-
.../tests/system/CalculateConnector.java | 257 +++++++++++++++++++++
.../nifi/processors/tests/system/Calculate.java | 71 ++++++
.../org.apache.nifi.components.connector.Connector | 1 +
.../services/org.apache.nifi.processor.Processor | 1 +
.../connectors/ConnectorMethodMarshallingIT.java | 71 ++++++
11 files changed, 559 insertions(+), 29 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
index 3823428856..7570cfe9e1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
@@ -16,6 +16,10 @@
*/
package org.apache.nifi.controller;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
import org.apache.nifi.annotation.behavior.Restricted;
@@ -140,7 +144,9 @@ import java.util.stream.Stream;
public class StandardProcessorNode extends ProcessorNode implements Connectable {
private static final Logger LOG = LoggerFactory.getLogger(StandardProcessorNode.class);
-
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
public static final String DEFAULT_YIELD_PERIOD = "1 sec";
@@ -1941,31 +1947,36 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
}
@Override
- public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final ProcessContext processContext) throws InvocationFailedException {
+ public String invokeConnectorMethod(final String methodName, final Map<String, String> jsonArguments, final ProcessContext processContext) throws InvocationFailedException {
final ConfigurableComponent component = getComponent();
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(getExtensionManager(), component.getClass(), getIdentifier())) {
final Method implementationMethod = discoverConnectorMethod(component.getClass(), methodName);
final MethodArgument[] methodArguments = getConnectorMethodArguments(methodName, implementationMethod, component);
final List<Object> argumentValues = new ArrayList<>();
+
for (final MethodArgument methodArgument : methodArguments) {
- final Object argumentValue = arguments.get(methodArgument.name());
if (ProcessContext.class.equals(methodArgument.type())) {
continue;
}
- if (argumentValue == null && methodArgument.required()) {
+ final String jsonValue = jsonArguments.get(methodArgument.name());
+ if (jsonValue == null && methodArgument.required()) {
throw new IllegalArgumentException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because the required argument '"
+ methodArgument.name() + "' was not provided");
}
- if (argumentValue != null && !(methodArgument.type().isAssignableFrom(argumentValue.getClass()))) {
- throw new IllegalArgumentException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because the argument '"
- + methodArgument.name() + "' is of type " + argumentValue.getClass().getName() + " defined by " + argumentValue.getClass().getClassLoader()
- + " but the method expects type " + methodArgument.type().getName() + " defined by " + methodArgument.type().getClassLoader());
+ if (jsonValue == null) {
+ argumentValues.add(null);
+ } else {
+ try {
+ final Object argumentValue = OBJECT_MAPPER.readValue(jsonValue, methodArgument.type());
+ argumentValues.add(argumentValue);
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to deserialize argument '" + methodArgument.name() + "' as type " + methodArgument.type().getName() +
+ " for Connector Method '" + methodName + "' on " + this, e);
+ }
}
-
- argumentValues.add(argumentValue);
}
// Inject ProcessContext if the method signature supports it
@@ -1979,7 +1990,14 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
try {
implementationMethod.setAccessible(true);
- return implementationMethod.invoke(component, argumentValues.toArray());
+ final Object result = implementationMethod.invoke(component, argumentValues.toArray());
+ if (result == null) {
+ return null;
+ }
+
+ return OBJECT_MAPPER.writeValueAsString(result);
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to serialize return value for Connector Method '" + methodName + "' on " + this, e);
} catch (final Exception e) {
throw new InvocationFailedException(e);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
index 57efd27d08..6bcc7a2c4d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
@@ -16,6 +16,10 @@
*/
package org.apache.nifi.controller.service;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.annotation.behavior.Restricted;
import org.apache.nifi.annotation.documentation.DeprecationNotice;
@@ -104,6 +108,9 @@ import java.util.stream.Collectors;
public class StandardControllerServiceNode extends AbstractComponentNode implements ControllerServiceNode {
private static final Logger LOG = LoggerFactory.getLogger(StandardControllerServiceNode.class);
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
private static final long INCREMENTAL_VALIDATION_DELAY_MS = 1000;
private static final Duration MAXIMUM_DELAY = Duration.ofMinutes(10);
@@ -904,37 +911,40 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
}
}
- // TODO: Refactor, this is the same between ProcessorNode / ControllerServiceNode except for getComponentState
@Override
- public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final ConfigurationContext configurationContext) throws InvocationFailedException {
+ public String invokeConnectorMethod(final String methodName, final Map<String, String> jsonArguments, final ConfigurationContext configurationContext) throws InvocationFailedException {
final ConfigurableComponent component = getComponent();
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(getExtensionManager(), component.getClass(), getIdentifier())) {
final Method implementationMethod = discoverConnectorMethod(component.getClass(), methodName);
final MethodArgument[] methodArguments = getConnectorMethodArguments(methodName, implementationMethod, component);
final List<Object> argumentValues = new ArrayList<>();
+
for (final MethodArgument methodArgument : methodArguments) {
- final Object argumentValue = arguments.get(methodArgument.name());
if (ConfigurationContext.class.equals(methodArgument.type())) {
continue;
}
- if (argumentValue == null && methodArgument.required()) {
+ final String jsonValue = jsonArguments.get(methodArgument.name());
+ if (jsonValue == null && methodArgument.required()) {
throw new IllegalArgumentException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because the required argument '"
+ methodArgument.name() + "' was not provided");
}
- if (argumentValue != null && !(methodArgument.type().isAssignableFrom(argumentValue.getClass()))) {
- throw new IllegalArgumentException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because the argument '"
- + methodArgument.name() + "' is of type " + argumentValue.getClass().getName() + " defined by " + argumentValue.getClass().getClassLoader()
- + " but the method expects type " + methodArgument.type().getName() + " defined by " + methodArgument.type().getClassLoader());
+ if (jsonValue == null) {
+ argumentValues.add(null);
+ } else {
+ try {
+ final Object argumentValue = OBJECT_MAPPER.readValue(jsonValue, methodArgument.type());
+ argumentValues.add(argumentValue);
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to deserialize argument '" + methodArgument.name() + "' as type " + methodArgument.type().getName()
+ + " for Connector Method '" + methodName + "' on " + this, e);
+ }
}
-
- argumentValues.add(argumentValue);
}
// Inject ConfigurationContext if the method signature supports it
- // TODO: Can we move away from Maps and instead just use reflection to get the arguments directly?
final Class<?>[] argumentTypes = implementationMethod.getParameterTypes();
if (argumentTypes.length > 0 && ConfigurationContext.class.isAssignableFrom(argumentTypes[0])) {
argumentValues.addFirst(configurationContext);
@@ -945,7 +955,14 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
try {
implementationMethod.setAccessible(true);
- return implementationMethod.invoke(component, argumentValues.toArray());
+ final Object result = implementationMethod.invoke(component, argumentValues.toArray());
+ if (result == null) {
+ return null;
+ }
+
+ return OBJECT_MAPPER.writeValueAsString(result);
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to serialize return value for Connector Method '" + methodName + "' on " + this, e);
} catch (final Exception e) {
throw new InvocationFailedException(e);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessorNode.java
index 001fae6785..fce8eeb21e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessorNode.java
@@ -312,7 +312,7 @@ public abstract class ProcessorNode extends AbstractComponentNode implements Con
public abstract void migrateConfiguration(Map<String, String> originalPropertyValues, ControllerServiceFactory serviceFactory);
- public abstract Object invokeConnectorMethod(String methodName, Map<String, Object> arguments, ProcessContext processContext) throws InvocationFailedException;
+ public abstract String invokeConnectorMethod(String methodName, Map<String, String> jsonArguments, ProcessContext processContext) throws InvocationFailedException;
public abstract List<ConnectorMethod> getConnectorMethods();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
index 2b2f78723e..16b8b7e8d3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
@@ -263,7 +263,7 @@ public interface ControllerServiceNode extends ComponentNode, VersionedComponent
void migrateConfiguration(Map<String, String> originalPropertyValues, ControllerServiceFactory serviceFactory);
- Object invokeConnectorMethod(String methodName, Map<String, Object> arguments, ConfigurationContext configurationContext) throws InvocationFailedException;
+ String invokeConnectorMethod(String methodName, Map<String, String> jsonArguments, ConfigurationContext configurationContext) throws InvocationFailedException;
List<ConnectorMethod> getConnectorMethods();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceFacade.java
index 1ec92cad5c..59cbb92516 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceFacade.java
@@ -17,6 +17,10 @@
package org.apache.nifi.components.connector.facades.standalone;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ValidationContext;
@@ -39,10 +43,15 @@ import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterLookup;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class StandaloneControllerServiceFacade implements ControllerServiceFacade {
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
+
private final ControllerServiceNode controllerServiceNode;
private final VersionedControllerService versionedControllerService;
private final ParameterContext parameterContext;
@@ -161,12 +170,49 @@ public class StandaloneControllerServiceFacade implements ControllerServiceFacad
@Override
public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments) throws InvocationFailedException {
+ final Map<String, String> jsonArguments = serializeArgumentsToJson(arguments);
final ConfigurationContext configurationContext = componentContextProvider.createConfigurationContext(controllerServiceNode, parameterContext);
- return controllerServiceNode.invokeConnectorMethod(methodName, arguments, configurationContext);
+ final String jsonResult = controllerServiceNode.invokeConnectorMethod(methodName, jsonArguments, configurationContext);
+ if (jsonResult == null) {
+ return null;
+ }
+
+ try {
+ return OBJECT_MAPPER.readValue(jsonResult, Object.class);
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to deserialize return value from Connector Method '" + methodName + "'", e);
+ }
}
@Override
public <T> T invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final Class<T> returnType) throws InvocationFailedException {
- return returnType.cast(invokeConnectorMethod(methodName, arguments));
+ final Map<String, String> jsonArguments = serializeArgumentsToJson(arguments);
+ final ConfigurationContext configurationContext = componentContextProvider.createConfigurationContext(controllerServiceNode, parameterContext);
+ final String jsonResult = controllerServiceNode.invokeConnectorMethod(methodName, jsonArguments, configurationContext);
+ if (jsonResult == null) {
+ return null;
+ }
+
+ try {
+ return OBJECT_MAPPER.readValue(jsonResult, returnType);
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to deserialize return value from Connector Method '" + methodName + "'", e);
+ }
+ }
+
+ private Map<String, String> serializeArgumentsToJson(final Map<String, Object> arguments) throws InvocationFailedException {
+ final Map<String, String> jsonArguments = new HashMap<>();
+ for (final Map.Entry<String, Object> entry : arguments.entrySet()) {
+ if (entry.getValue() == null) {
+ jsonArguments.put(entry.getKey(), null);
+ } else {
+ try {
+ jsonArguments.put(entry.getKey(), OBJECT_MAPPER.writeValueAsString(entry.getValue()));
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to serialize argument '" + entry.getKey() + "' to JSON", e);
+ }
+ }
+ }
+ return jsonArguments;
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneProcessorFacade.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneProcessorFacade.java
index 5fe3f9ef83..707aa8b67c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneProcessorFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneProcessorFacade.java
@@ -17,6 +17,10 @@
package org.apache.nifi.components.connector.facades.standalone;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ValidationContext;
@@ -41,10 +45,15 @@ import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.processor.ProcessContext;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class StandaloneProcessorFacade implements ProcessorFacade {
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
+
private final ProcessorNode processorNode;
private final VersionedProcessor versionedProcessor;
private final ParameterContext parameterContext;
@@ -169,12 +178,51 @@ public class StandaloneProcessorFacade implements ProcessorFacade {
@Override
public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments) throws InvocationFailedException {
+ final Map<String, String> jsonArguments = serializeArgumentsToJson(arguments);
final ProcessContext processContext = componentContextProvider.createProcessContext(processorNode, parameterContext);
- return processorNode.invokeConnectorMethod(methodName, arguments, processContext);
+ final String jsonResult = processorNode.invokeConnectorMethod(methodName, jsonArguments, processContext);
+ if (jsonResult == null) {
+ return null;
+ }
+
+ try {
+ return OBJECT_MAPPER.readValue(jsonResult, Object.class);
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to deserialize return value from Connector Method '" + methodName + "'", e);
+ }
}
@Override
public <T> T invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final Class<T> returnType) throws InvocationFailedException {
- return returnType.cast(invokeConnectorMethod(methodName, arguments));
+ final Map<String, String> jsonArguments = serializeArgumentsToJson(arguments);
+ final ProcessContext processContext = componentContextProvider.createProcessContext(processorNode, parameterContext);
+ final String jsonResult = processorNode.invokeConnectorMethod(methodName, jsonArguments, processContext);
+ if (jsonResult == null) {
+ return null;
+ }
+
+ try {
+ return OBJECT_MAPPER.readValue(jsonResult, returnType);
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to deserialize return value from Connector Method '" + methodName + "'", e);
+ }
+ }
+
+ private Map<String, String> serializeArgumentsToJson(final Map<String, Object> arguments) throws InvocationFailedException {
+ final Map<String, String> jsonArguments = new HashMap<>();
+
+ for (final Map.Entry<String, Object> entry : arguments.entrySet()) {
+ if (entry.getValue() == null) {
+ jsonArguments.put(entry.getKey(), null);
+ } else {
+ try {
+ jsonArguments.put(entry.getKey(), OBJECT_MAPPER.writeValueAsString(entry.getValue()));
+ } catch (final JsonProcessingException e) {
+ throw new InvocationFailedException("Failed to serialize argument '" + entry.getKey() + "' to JSON", e);
+ }
+ }
+ }
+
+ return jsonArguments;
}
}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java
new file mode 100644
index 0000000000..db43577038
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java
@@ -0,0 +1,257 @@
+/*
+ * 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.connectors.tests.system;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.connector.AbstractConnector;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.InvocationFailedException;
+import org.apache.nifi.components.connector.PropertyType;
+import org.apache.nifi.components.connector.StepConfigurationContext;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.components.connector.components.ProcessorFacade;
+import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.flow.Position;
+import org.apache.nifi.flow.ScheduledState;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A test connector that invokes a ConnectorMethod on a CalculateProcessor using its own POJO types.
+ * This connector is used to test JSON marshalling of complex objects across ClassLoader boundaries.
+ */
+public class CalculateConnector extends AbstractConnector {
+
+ /**
+ * A POJO representing a calculation request. This is intentionally a different class than
+ * the Processor's record type to test cross-ClassLoader JSON marshalling.
+ */
+ public static class Calculation {
+ private int operand1;
+ private int operand2;
+ private String operation;
+
+ public Calculation() {
+ }
+
+ public Calculation(final int operand1, final int operand2, final String operation) {
+ this.operand1 = operand1;
+ this.operand2 = operand2;
+ this.operation = operation;
+ }
+
+ public int getOperand1() {
+ return operand1;
+ }
+
+ public void setOperand1(final int operand1) {
+ this.operand1 = operand1;
+ }
+
+ public int getOperand2() {
+ return operand2;
+ }
+
+ public void setOperand2(final int operand2) {
+ this.operand2 = operand2;
+ }
+
+ public String getOperation() {
+ return operation;
+ }
+
+ public void setOperation(final String operation) {
+ this.operation = operation;
+ }
+ }
+
+ /**
+ * A POJO representing the result of a calculation. This is intentionally a different class than
+ * the Processor's record type to test cross-ClassLoader JSON marshalling.
+ */
+ public static class CalculatedResult {
+ private Calculation calculation;
+ private int result;
+
+ public CalculatedResult() {
+ }
+
+ public Calculation getCalculation() {
+ return calculation;
+ }
+
+ public void setCalculation(final Calculation calculation) {
+ this.calculation = calculation;
+ }
+
+ public int getResult() {
+ return result;
+ }
+
+ public void setResult(final int result) {
+ this.result = result;
+ }
+ }
+
+ private static final ConnectorPropertyDescriptor OPERAND_1 = new ConnectorPropertyDescriptor.Builder()
+ .name("Operand 1")
+ .description("The first operand for the calculation")
+ .type(PropertyType.STRING)
+ .required(true)
+ .addValidator(StandardValidators.INTEGER_VALIDATOR)
+ .build();
+
+ private static final ConnectorPropertyDescriptor OPERAND_2 = new ConnectorPropertyDescriptor.Builder()
+ .name("Operand 2")
+ .description("The second operand for the calculation")
+ .type(PropertyType.STRING)
+ .required(true)
+ .addValidator(StandardValidators.INTEGER_VALIDATOR)
+ .build();
+
+ private static final ConnectorPropertyDescriptor OPERATION = new ConnectorPropertyDescriptor.Builder()
+ .name("Operation")
+ .description("The operation to perform (ADD, SUBTRACT, MULTIPLY, DIVIDE)")
+ .type(PropertyType.STRING)
+ .required(true)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ private static final ConnectorPropertyDescriptor OUTPUT_FILE = new ConnectorPropertyDescriptor.Builder()
+ .name("Output File")
+ .description("The file to write the calculation result to")
+ .type(PropertyType.STRING)
+ .required(true)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ private static final ConnectorPropertyGroup CALCULATION_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Calculation Configuration")
+ .description("Configuration properties for the calculation")
+ .properties(List.of(OPERAND_1, OPERAND_2, OPERATION, OUTPUT_FILE))
+ .build();
+
+ private static final ConfigurationStep CALCULATION_STEP = new ConfigurationStep.Builder()
+ .name("Calculation")
+ .description("Configure the calculation parameters")
+ .propertyGroups(List.of(CALCULATION_GROUP))
+ .build();
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ final VersionedProcessGroup group = new VersionedProcessGroup();
+ group.setName("Calculate Flow");
+ group.setIdentifier("calculate-flow-id");
+
+ final Bundle bundle = new Bundle();
+ bundle.setGroup("org.apache.nifi");
+ bundle.setArtifact("nifi-system-test-extensions-nar");
+ bundle.setVersion("2.7.0-SNAPSHOT");
+
+ final VersionedProcessor processor = new VersionedProcessor();
+ processor.setIdentifier("calculate-processor-id");
+ processor.setName("Calculate Processor");
+ processor.setType("org.apache.nifi.processors.tests.system.Calculate");
+ processor.setBundle(bundle);
+ processor.setProperties(Map.of());
+ processor.setPropertyDescriptors(Map.of());
+ processor.setScheduledState(ScheduledState.ENABLED);
+ processor.setBulletinLevel("WARN");
+ processor.setPosition(new Position(0D, 0D));
+ processor.setPenaltyDuration("30 sec");
+ processor.setAutoTerminatedRelationships(Set.of());
+ processor.setExecutionNode("ALL");
+ processor.setGroupIdentifier(group.getIdentifier());
+ processor.setConcurrentlySchedulableTaskCount(1);
+ processor.setRunDurationMillis(0L);
+ processor.setSchedulingStrategy("TIMER_DRIVEN");
+ processor.setYieldDuration("1 sec");
+ processor.setSchedulingPeriod("0 sec");
+ processor.setStyle(Map.of());
+ group.setProcessors(Set.of(processor));
+
+ final VersionedExternalFlow flow = new VersionedExternalFlow();
+ flow.setFlowContents(group);
+ flow.setParameterContexts(Map.of());
+ return flow;
+ }
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return List.of(CALCULATION_STEP);
+ }
+
+ @Override
+ protected void onStepConfigured(final String stepName, final FlowContext workingContext) {
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
+ final StepConfigurationContext stepContext = workingContext.getConfigurationContext().scopedToStep(CALCULATION_STEP);
+ final int operand1 = stepContext.getProperty(OPERAND_1).asInteger();
+ final int operand2 = stepContext.getProperty(OPERAND_2).asInteger();
+ final String operation = stepContext.getProperty(OPERATION).getValue();
+ final String outputFile = stepContext.getProperty(OUTPUT_FILE).getValue();
+
+ final ProcessorFacade processorFacade = workingContext.getRootGroup().getProcessors().stream()
+ .filter(p -> p.getDefinition().getType().endsWith("Calculate"))
+ .findFirst()
+ .orElseThrow(() -> new FlowUpdateException("CalculateProcessor not found in flow"));
+
+ final Calculation calculation = new Calculation(operand1, operand2, operation);
+ final CalculatedResult result;
+ try {
+ result = processorFacade.invokeConnectorMethod("calculate", Map.of("calculation", calculation), CalculatedResult.class);
+ } catch (final InvocationFailedException e) {
+ throw new FlowUpdateException("Failed to invoke calculate method", e);
+ }
+
+ final File file = new File(outputFile);
+ try (final FileWriter writer = new FileWriter(file)) {
+ writer.write(String.valueOf(result.getCalculation().getOperand1()));
+ writer.write("\n");
+ writer.write(String.valueOf(result.getCalculation().getOperand2()));
+ writer.write("\n");
+ writer.write(result.getCalculation().getOperation());
+ writer.write("\n");
+ writer.write(String.valueOf(result.getResult()));
+ } catch (final IOException e) {
+ throw new FlowUpdateException("Failed to write result to file", e);
+ }
+
+ getLogger().info("Calculation result: {} {} {} = {}", operand1, operation, operand2, result.getResult());
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> propertyValueOverrides, final FlowContext flowContext) {
+ return List.of();
+ }
+}
+
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/Calculate.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/Calculate.java
new file mode 100644
index 0000000000..d8bc65b8a5
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/Calculate.java
@@ -0,0 +1,71 @@
+/*
+ * 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.processors.tests.system;
+
+import org.apache.nifi.components.connector.components.ComponentState;
+import org.apache.nifi.components.connector.components.ConnectorMethod;
+import org.apache.nifi.components.connector.components.MethodArgument;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.exception.ProcessException;
+
+/**
+ * A test processor that exposes a ConnectorMethod for performing calculations.
+ * This processor is used to test JSON marshalling of complex objects across ClassLoader boundaries.
+ */
+public class Calculate extends AbstractProcessor {
+
+ @ConnectorMethod(
+ name = "calculate",
+ description = "Performs a calculation based on the provided Calculation object",
+ allowedStates = ComponentState.STOPPED,
+ arguments = {
+ @MethodArgument(name = "calculation", type = Calculation.class, description = "The calculation to perform", required = true)
+ }
+ )
+ public Object calculate(final Calculation calculation) {
+ final int result = switch (calculation.operation()) {
+ case "ADD" -> calculation.operand1() + calculation.operand2();
+ case "SUBTRACT" -> calculation.operand1() - calculation.operand2();
+ case "MULTIPLY" -> calculation.operand1() * calculation.operand2();
+ case "DIVIDE" -> calculation.operand1() / calculation.operand2();
+ default -> throw new IllegalArgumentException("Unknown operation: " + calculation.operation());
+ };
+
+ return new CalculatedResult(calculation, result);
+ }
+
+ @Override
+ public void onTrigger(final ProcessContext processContext, final ProcessSession processSession) throws ProcessException {
+ }
+
+ /**
+ * A record representing a calculation request with two operands and an operation.
+ */
+ public record Calculation(int operand1, int operand2, String operation) {
+ }
+
+ /**
+ * A record representing the result of a calculation, including the original calculation and the result.
+ */
+ public record CalculatedResult(Calculation calculation, int result) {
+ }
+
+}
+
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
index a54f75d132..34fe7c90ea 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -17,3 +17,4 @@ org.apache.nifi.connectors.tests.system.NopConnector
org.apache.nifi.connectors.tests.system.AssetConnector
org.apache.nifi.connectors.tests.system.DataQueuingConnector
org.apache.nifi.connectors.tests.system.NestedProcessGroupConnector
+org.apache.nifi.connectors.tests.system.CalculateConnector
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index d0a01b38b5..93e561e6d8 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -62,3 +62,4 @@ org.apache.nifi.processors.tests.system.WriteFlowFileCountToFile
org.apache.nifi.processors.tests.system.WriteLifecycleEvents
org.apache.nifi.processors.tests.system.WriteToFile
org.apache.nifi.processors.tests.system.YieldSource
+org.apache.nifi.processors.tests.system.Calculate
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorMethodMarshallingIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorMethodMarshallingIT.java
new file mode 100644
index 0000000000..e57bf852d2
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorMethodMarshallingIT.java
@@ -0,0 +1,71 @@
+/*
+ * 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.tests.system.connectors;
+
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * System tests that verify JSON marshalling of complex objects across ClassLoader boundaries
+ * when invoking ConnectorMethods on Processors.
+ */
+public class ConnectorMethodMarshallingIT extends NiFiSystemIT {
+
+ @Test
+ public void testComplexObjectMarshalling() throws NiFiClientException, IOException, InterruptedException {
+ final File outputFile = new File("target/calculate-result.txt");
+ if (outputFile.exists()) {
+ assertTrue(outputFile.delete(), "Failed to delete existing output file");
+ }
+
+ final ConnectorEntity connector = getClientUtil().createConnector("CalculateConnector");
+ assertNotNull(connector);
+
+ getClientUtil().configureConnector(connector, "Calculation", Map.of(
+ "Operand 1", "10",
+ "Operand 2", "5",
+ "Operation", "ADD",
+ "Output File", outputFile.getAbsolutePath()
+ ));
+
+ getClientUtil().applyConnectorUpdate(connector);
+ getClientUtil().waitForValidConnector(connector.getId());
+
+ assertTrue(outputFile.exists(), "Output file was not created");
+ final String fileContents = Files.readString(outputFile.toPath(), StandardCharsets.UTF_8);
+ final String[] lines = fileContents.split("\n");
+ assertEquals(4, lines.length, "Output file should contain 4 lines");
+ assertEquals("10", lines[0], "First line should be operand1");
+ assertEquals("5", lines[1], "Second line should be operand2");
+ assertEquals("ADD", lines[2], "Third line should be the operation");
+ assertEquals("15", lines[3], "Fourth line should be the result (10 + 5 = 15)");
+ }
+}
+
(nifi) 18/40: NIFI-15367: Ensure that Connectors' implicit parameter contexts are n… (#10665)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit e60e994d4ecc7d9bed842e2d8c1bb1bf47a2184b
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Dec 19 14:47:18 2025 -0500
NIFI-15367: Ensure that Connectors' implicit parameter contexts are n… (#10665)
* NIFI-15367: Ensure that Connectors' implicit parameter contexts are not registered with ParameterContextManager; added system test to verify; fixed bug in ConnectorAssetsIT where it did not wait for connector to return to its 'STOPPED' state before attempting to delete
* NIFI-15367: Bug fixes; review feedback
---
.../nifi/controller/flow/AbstractFlowManager.java | 34 ++++++++++++++++++++--
.../apache/nifi/controller/flow/FlowManager.java | 25 +++++++++++-----
.../connector/StandardConnectorRepository.java | 2 ++
.../components/connector/StandardFlowContext.java | 8 +++--
.../flow/FlowControllerFlowContextFactory.java | 12 ++++++--
.../nifi/controller/flow/StandardFlowManager.java | 4 +--
.../controller/flow/TestStandardFlowManager.java | 1 +
.../web/dao/impl/StandardConnectorDAOTest.java | 11 +++++++
.../nifi/connectors/tests/system/NopConnector.java | 17 +++++++++--
.../apache/nifi/tests/system/NiFiClientUtil.java | 21 +++++++++++++
.../tests/system/connectors/ConnectorAssetsIT.java | 2 ++
.../tests/system/connectors/ConnectorCrudIT.java | 5 ++++
12 files changed, 122 insertions(+), 20 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
index a08000428c..a694c5be35 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
@@ -57,6 +57,7 @@ import org.apache.nifi.validation.RuleViolationsManager;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -628,8 +629,16 @@ public abstract class AbstractFlowManager implements FlowManager {
@Override
public ParameterContext createParameterContext(final String id, final String name, final String description,
- final Map<String, Parameter> parameters, final List<String> inheritedContextIds,
- final ParameterProviderConfiguration parameterProviderConfiguration) {
+ final Map<String, Parameter> parameters, final List<String> inheritedContextIds,
+ final ParameterProviderConfiguration parameterProviderConfiguration) {
+
+ return createParameterContext(id, name, description, parameters, inheritedContextIds, parameterProviderConfiguration, true);
+ }
+
+ protected ParameterContext createParameterContext(final String id, final String name, final String description,
+ final Map<String, Parameter> parameters, final List<String> inheritedContextIds,
+ final ParameterProviderConfiguration parameterProviderConfiguration, final boolean register) {
+
final boolean namingConflict = parameterContextManager.getParameterContexts().stream()
.anyMatch(paramContext -> paramContext.getName().equals(name));
@@ -660,10 +669,29 @@ public abstract class AbstractFlowManager implements FlowManager {
parameterContext.setInheritedParameterContexts(parameterContextList);
}
- parameterContextManager.addParameterContext(parameterContext);
+ if (register) {
+ parameterContextManager.addParameterContext(parameterContext);
+ }
+
return parameterContext;
}
+ @Override
+ public ParameterContext duplicateParameterContext(final String id, final ParameterContext source) {
+ final Map<String, Parameter> parameterMap = new HashMap<>();
+ for (final Parameter parameter : source.getParameters().values()) {
+ parameterMap.put(parameter.getDescriptor().getName(), parameter);
+ }
+
+ final List<String> inheritedContextIds = new ArrayList<>();
+ for (final ParameterContext inherited : source.getInheritedParameterContexts()) {
+ inheritedContextIds.add(inherited.getIdentifier());
+ }
+
+ return createParameterContext(id, source.getName(), source.getDescription(),
+ parameterMap, inheritedContextIds, source.getParameterProviderConfiguration(), false);
+ }
+
@Override
public void withParameterContextResolution(final Runnable parameterContextAction) {
withParameterContextResolution.set(true);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
index 8ef1ed6cff..266e9888d2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
@@ -360,20 +360,29 @@ public interface FlowManager extends ParameterProviderLookup {
* <code>IllegalStateException</code> is thrown. See {@link FlowManager#withParameterContextResolution(Runnable)}
* for example usage.
*
- * @param id The unique id
- * @param name The ParameterContext name
- * @param description The ParameterContext description
- * @param parameters The Parameters
- * @param inheritedContextIds The identifiers of any Parameter Contexts that the newly created Parameter Context should inherit from. The order of the identifiers in the List determines the
+ * @param id the unique id
+ * @param name the ParameterContext name
+ * @param description the ParameterContext description
+ * @param parameters the Parameters
+ * @param inheritedContextIds the identifiers of any Parameter Contexts that the newly created Parameter Context should inherit from. The order of the identifiers in the List determines the
* order in which parameters with conflicting names are resolved. I.e., the Parameter Context whose ID comes first in the List is preferred.
- * @param parameterProviderConfiguration Optional configuration for a ParameterProvider
- * @return The created ParameterContext
- * @throws IllegalStateException If <code>parameterContexts</code> is not empty and this method is called without being wrapped
+ * @param parameterProviderConfiguration optional configuration for a ParameterProvider
+ * @return the created ParameterContext
+ * @throws IllegalStateException if <code>parameterContexts</code> is not empty and this method is called without being wrapped
* by {@link FlowManager#withParameterContextResolution(Runnable)}
*/
ParameterContext createParameterContext(String id, String name, String description, Map<String, Parameter> parameters,
List<String> inheritedContextIds, ParameterProviderConfiguration parameterProviderConfiguration);
+ /**
+ * Creates a duplicate of the given ParameterContext with the provided id. This does not register the Parameter Context
+ * with the ParameterContextManager.
+ * @param id the id of the new ParameterContext
+ * @param source the ParameterContext to duplicate
+ * @return the duplicated ParameterContext
+ */
+ ParameterContext duplicateParameterContext(String id, ParameterContext source);
+
/**
* Performs the given ParameterContext-related action, and then resolves all inherited ParameterContext references.
* Example usage: <br/><br/>
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index dfa7a91a94..34fe990aa1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -147,8 +147,10 @@ public class StandardConnectorRepository implements ConnectorRepository {
// If the initial desired state was RUNNING, start the connector again. Otherwise, stop it.
// We don't simply leave it be as the prepareForUpdate / update may have changed the state of some components.
if (initialDesiredState == ConnectorState.RUNNING) {
+ logger.info("Connector {} has been successfully updated; starting Connector to resume initial state", connector);
connector.start(lifecycleExecutor);
} else {
+ logger.info("Connector {} has been successfully updated; stopping Connector to resume initial state", connector);
connector.stop(lifecycleExecutor);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardFlowContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardFlowContext.java
index ea855e6215..16ebbc3e39 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardFlowContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardFlowContext.java
@@ -27,6 +27,8 @@ import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
+import java.util.Collections;
+
public class StandardFlowContext implements FrameworkFlowContext {
private final ProcessGroup managedProcessGroup;
private final MutableConnectorConfigurationContext configurationContext;
@@ -83,7 +85,10 @@ public class StandardFlowContext implements FrameworkFlowContext {
throw new FlowUpdateException("Flow is not in a state that allows the requested updated", e);
}
- managedProcessGroup.updateFlow(versionedExternalFlow, managedProcessGroup.getIdentifier(), false, true, true);
+ final VersionedExternalFlow withoutParameterContext = new VersionedExternalFlow();
+ withoutParameterContext.setFlowContents(versionedExternalFlow.getFlowContents());
+ withoutParameterContext.setParameterContexts(Collections.emptyMap());
+ managedProcessGroup.updateFlow(withoutParameterContext, managedProcessGroup.getIdentifier(), false, true, true);
final ConnectorParameterLookup parameterLookup = new ConnectorParameterLookup(versionedExternalFlow.getParameterContexts().values(), assetManager);
getParameterContext().updateParameters(parameterLookup.getParameterValues());
@@ -101,7 +106,6 @@ public class StandardFlowContext implements FrameworkFlowContext {
}
}
-
@Override
public Bundle getBundle() {
return bundle;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
index c488caf03d..b7ed11d323 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/FlowControllerFlowContextFactory.java
@@ -29,9 +29,9 @@ import org.apache.nifi.components.connector.facades.standalone.StandaloneParamet
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.VersionedExternalFlow;
-import org.apache.nifi.flow.VersionedParameterContext;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.registry.flow.mapping.ComponentIdLookup;
import org.apache.nifi.registry.flow.mapping.FlowMappingOptions;
import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedProcessGroup;
@@ -96,15 +96,21 @@ public class FlowControllerFlowContextFactory implements FlowContextFactory {
.build();
final VersionedComponentFlowMapper flowMapper = new VersionedComponentFlowMapper(flowController.getExtensionManager(), flowMappingOptions);
- final Map<String, VersionedParameterContext> parameterContexts = flowMapper.mapParameterContexts(sourceGroup, true, Map.of());
final InstantiatedVersionedProcessGroup versionedGroup = flowMapper.mapProcessGroup(sourceGroup, flowController.getControllerServiceProvider(),
flowController.getFlowManager(), true);
final VersionedExternalFlow versionedExternalFlow = new VersionedExternalFlow();
versionedExternalFlow.setFlowContents(versionedGroup);
versionedExternalFlow.setExternalControllerServices(Map.of());
versionedExternalFlow.setParameterProviders(Map.of());
- versionedExternalFlow.setParameterContexts(parameterContexts);
+ versionedExternalFlow.setParameterContexts(Map.of());
destinationGroup.updateFlow(versionedExternalFlow, componentIdSeed, false, true, true);
+
+ final String duplicateContextId = UUID.nameUUIDFromBytes((destinationGroup.getIdentifier() + "-param-context").getBytes(StandardCharsets.UTF_8)).toString();
+ final ParameterContext sourceContext = sourceGroup.getParameterContext();
+ if (sourceContext != null) {
+ final ParameterContext duplicateParameterContext = flowController.getFlowManager().duplicateParameterContext(duplicateContextId, sourceContext);
+ destinationGroup.setParameterContext(duplicateParameterContext);
+ }
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index c48288bd71..7cf4bc8707 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -757,9 +757,9 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
final String paramContextId = UUID.nameUUIDFromBytes((id + "-parameter-context").getBytes(StandardCharsets.UTF_8)).toString();
final String paramContextName = "Connector " + id + " Parameter Context";
+ final String parameterContextDescription = "Implicit Parameter Context for Connector " + id;
final ParameterContext managedParameterContext = createParameterContext(paramContextId, paramContextName,
- "Implicit Parameter Context for Connector " + id,
- Collections.emptyMap(), Collections.emptyList(), null);
+ parameterContextDescription, Collections.emptyMap(), Collections.emptyList(), null, false);
managedRootGroup.setParameterContext(managedParameterContext);
final ConnectorRepository connectorRepository = flowController.getConnectorRepository();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/TestStandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/TestStandardFlowManager.java
index 9f11f77c60..33ec45aac0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/TestStandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/TestStandardFlowManager.java
@@ -95,6 +95,7 @@ public class TestStandardFlowManager {
when(bundle.getClassLoader()).thenReturn(NopConnector.class.getClassLoader());
flowManager = new StandardFlowManager(nifiProperties, sslContext, flowController, flowFileEventRepository, parameterContextManager);
+ when(flowController.getFlowManager()).thenReturn(flowManager);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
index f3c480efd5..3f13d8f569 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
@@ -33,6 +33,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
import java.util.Collections;
import java.util.List;
@@ -41,11 +43,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
class StandardConnectorDAOTest {
private StandardConnectorDAO connectorDAO;
@@ -84,6 +88,13 @@ class StandardConnectorDAOTest {
connectorDAO.setFlowController(flowController);
when(flowController.getConnectorRepository()).thenReturn(connectorRepository);
+ when(connectorRepository.getAssetRepository()).thenReturn(connectorAssetRepository);
+
+ final MutableConnectorConfigurationContext configContext = mock(MutableConnectorConfigurationContext.class);
+ when(configContext.toConnectorConfiguration()).thenReturn(mock(ConnectorConfiguration.class));
+ final FrameworkFlowContext activeContext = mock(FrameworkFlowContext.class);
+ when(activeContext.getConfigurationContext()).thenReturn(configContext);
+ when(connectorNode.getActiveFlowContext()).thenReturn(activeContext);
}
@Test
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java
index 702a4574ad..3d28240ca2 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java
@@ -29,12 +29,14 @@ import org.apache.nifi.components.connector.ConnectorPropertyGroup;
import org.apache.nifi.components.connector.PropertyType;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedParameter;
+import org.apache.nifi.flow.VersionedParameterContext;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.processor.util.StandardValidators;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
public class NopConnector extends AbstractConnector {
@@ -81,8 +83,19 @@ public class NopConnector extends AbstractConnector {
final VersionedProcessGroup group = new VersionedProcessGroup();
group.setName("Nop Flow");
+ final VersionedParameter someText = new VersionedParameter();
+ someText.setName("SOME_TEXT");
+ someText.setValue("Lorem ipsum");
+ someText.setSensitive(false);
+ someText.setProvided(false);
+ someText.setReferencedAssets(List.of());
+
+ final VersionedParameterContext parameterContext = new VersionedParameterContext();
+ parameterContext.setName("Nop Parameter Context");
+ parameterContext.setParameters(Set.of(someText));
+
final VersionedExternalFlow flow = new VersionedExternalFlow();
- flow.setParameterContexts(Collections.emptyMap());
+ flow.setParameterContexts(Map.of(parameterContext.getName(), parameterContext));
flow.setFlowContents(group);
return flow;
}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
index 28b43e150c..7a16c754c9 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
@@ -397,6 +397,27 @@ public class NiFiClientUtil {
}
}
+ public void waitForConnectorStopped(final String connectorId) throws NiFiClientException, IOException, InterruptedException {
+ waitForConnectorState(connectorId, ConnectorState.STOPPED);
+ }
+
+ public void waitForConnectorState(final String connectorId, final ConnectorState desiredState) throws InterruptedException, NiFiClientException, IOException {
+ int iteration = 0;
+ while (true) {
+ final ConnectorEntity entity = getConnectorClient().getConnector(connectorId);
+ final String state = entity.getComponent().getState();
+ if (desiredState.name().equals(state)) {
+ return;
+ }
+
+ if (iteration++ % 30 == 0) { // Every 3 seconds log status
+ logger.info("Connector with ID {} has state {} but waiting for state {}.", connectorId, state, desiredState);
+ }
+
+ Thread.sleep(100L);
+ }
+ }
+
public ParameterProviderEntity createParameterProvider(final String simpleTypeName) throws NiFiClientException, IOException {
return createParameterProvider(NiFiSystemIT.TEST_PARAM_PROVIDERS_PACKAGE + "." + simpleTypeName, NiFiSystemIT.NIFI_GROUP_ID, NiFiSystemIT.TEST_EXTENSIONS_ARTIFACT_ID, nifiVersion);
}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorAssetsIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorAssetsIT.java
index 59b46ddd82..3a83731776 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorAssetsIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorAssetsIT.java
@@ -195,6 +195,8 @@ public class ConnectorAssetsIT extends NiFiSystemIT {
assertFalse(assetStillPresent);
+ // Wait for Connector to stop before attempting to delete it.
+ getClientUtil().waitForConnectorStopped(connectorAfterApply.getId());
connectorClient.deleteConnector(connectorAfterApply);
}
}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
index 0179c17496..085177dad4 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorCrudIT.java
@@ -24,6 +24,7 @@ import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ParameterContextsEntity;
import org.apache.nifi.web.api.entity.ParameterProviderEntity;
import org.junit.jupiter.api.Test;
@@ -43,6 +44,10 @@ public class ConnectorCrudIT extends NiFiSystemIT {
final ConnectorEntity connector = getClientUtil().createConnector("NopConnector");
assertNotNull(connector);
+ // Ensure that Parameter Context is not created for the Connector
+ final ParameterContextsEntity contextsEntity = getNifiClient().getParamContextClient().getParamContexts();
+ assertEquals(0, contextsEntity.getParameterContexts().size());
+
// Configure the connector and apply the configuration
getClientUtil().configureConnector(connector, "Ignored Step", Map.of("Ignored Property", "Hello, World!"));
getClientUtil().applyConnectorUpdate(connector);
(nifi) 23/40: NIFI-15430: Ensure that if we fail to initialize a Connector, we crea… (#10733)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit dac8e8a013a1ad875f0dca80eb386c1e6f99e0b6
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Wed Jan 7 16:49:07 2026 -0500
NIFI-15430: Ensure that if we fail to initialize a Connector, we crea… (#10733)
* NIFI-15430: Ensure that if we fail to initialize a Connector, we create a GhostConnector instead and ensure that we also proivde the extensionMissing flag on ConnectorNode
* NIFI-15430: Added extensionMissing flag to Connector DTO
* NIFI-15430: If unable to load initial flow of a Connector, make ghosted connector instead
---
.../org/apache/nifi/web/api/dto/ConnectorDTO.java | 10 +++
.../nifi/components/connector/ConnectorNode.java | 6 ++
.../connector/StandardConnectorNode.java | 9 ++-
.../apache/nifi/controller/ExtensionBuilder.java | 90 ++++++++++++++--------
.../nifi/controller/flow/StandardFlowManager.java | 11 +--
.../connector/TestStandardConnectorNode.java | 3 +-
.../org/apache/nifi/web/api/dto/DtoFactory.java | 19 +++--
7 files changed, 96 insertions(+), 52 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
index 96849a58a0..a2253fdfeb 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
@@ -37,6 +37,7 @@ public class ConnectorDTO extends ComponentDTO {
private Collection<String> validationErrors;
private String validationStatus;
private Boolean multipleVersionsAvailable;
+ private Boolean extensionMissing;
private String configurationUrl;
private String detailsUrl;
@@ -148,4 +149,13 @@ public class ConnectorDTO extends ComponentDTO {
public void setDetailsUrl(final String detailsUrl) {
this.detailsUrl = detailsUrl;
}
+
+ @Schema(description = "Whether the extension for this connector is missing.")
+ public Boolean getExtensionMissing() {
+ return extensionMissing;
+ }
+
+ public void setExtensionMissing(final Boolean extensionMissing) {
+ this.extensionMissing = extensionMissing;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index 9dd1fe36a1..b7206edbd3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -66,6 +66,12 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
BundleCoordinate getBundleCoordinate();
+ /**
+ * Returns whether or not the underlying extension is missing (i.e., the Connector is a GhostConnector).
+ * @return true if the extension is missing, false otherwise
+ */
+ boolean isExtensionMissing();
+
List<AllowableValue> fetchAllowableValues(String stepName, String propertyName);
List<AllowableValue> fetchAllowableValues(String stepName, String propertyName, String filter);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 59f2dfba10..14713ecee9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -89,6 +89,7 @@ public class StandardConnectorNode implements ConnectorNode {
private final AtomicReference<ValidationState> validationState = new AtomicReference<>(new ValidationState(ValidationStatus.VALIDATING, Collections.emptyList()));
private final ConnectorValidationTrigger validationTrigger;
+ private final boolean extensionMissing;
private volatile boolean triggerValidation = true;
private volatile FrameworkFlowContext workingFlowContext;
@@ -101,7 +102,7 @@ public class StandardConnectorNode implements ConnectorNode {
final Authorizable parentAuthorizable, final ConnectorDetails connectorDetails, final String componentType, final String componentCanonicalClass,
final MutableConnectorConfigurationContext configurationContext,
final ConnectorStateTransition stateTransition, final FlowContextFactory flowContextFactory,
- final ConnectorValidationTrigger validationTrigger) {
+ final ConnectorValidationTrigger validationTrigger, final boolean extensionMissing) {
this.identifier = identifier;
this.flowManager = flowManager;
@@ -114,6 +115,7 @@ public class StandardConnectorNode implements ConnectorNode {
this.stateTransition = stateTransition;
this.flowContextFactory = flowContextFactory;
this.validationTrigger = validationTrigger;
+ this.extensionMissing = extensionMissing;
this.name = connectorDetails.getConnector().getClass().getSimpleName();
@@ -531,6 +533,11 @@ public class StandardConnectorNode implements ConnectorNode {
return bundleCoordinate;
}
+ @Override
+ public boolean isExtensionMissing() {
+ return extensionMissing;
+ }
+
@Override
public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName) {
if (workingFlowContext == null) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index c1eac07a4c..54ed942e70 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -33,9 +33,9 @@ import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorDetails;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
-import org.apache.nifi.components.connector.ConnectorValidationTrigger;
import org.apache.nifi.components.connector.ConnectorPropertyGroup;
import org.apache.nifi.components.connector.ConnectorStateTransition;
+import org.apache.nifi.components.connector.ConnectorValidationTrigger;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowContextFactory;
import org.apache.nifi.components.connector.FrameworkConnectorInitializationContext;
@@ -476,37 +476,13 @@ public class ExtensionBuilder {
return flowAnalysisRuleNode;
}
- public ConnectorNode buildConnector() {
+ public ConnectorNode buildConnector(final boolean loadInitialFlow) {
requireNonNull(identifier, "Connector ID");
requireNonNull(type, "Connector Type");
requireNonNull(bundleCoordinate, "Bundle Coordinate");
requireNonNull(extensionManager, "Extension Manager");
requireNonNull(managedProcessGroup, "Managed Process Group");
- boolean creationSuccessful = true;
- Connector connector;
-
- try {
- connector = createConnector();
- } catch (final Exception e) {
- logger.error("Could not create Connector of type {} from {} for ID {} due to: {}; creating \"Ghost\" implementation", type, bundleCoordinate, identifier, e.getMessage(), e);
- connector = new GhostConnector(identifier, type);
- creationSuccessful = false;
- }
-
- final String componentType;
- if (creationSuccessful) {
- componentType = connector.getClass().getSimpleName();
- } else {
- final String simpleClassName = type.contains(".") ? StringUtils.substringAfterLast(type, ".") : type;
- componentType = "(Missing) " + simpleClassName;
- }
-
- final ComponentLog componentLog = new SimpleProcessLogger(identifier, connector, new StandardLoggingContext());
- final ConnectorDetails connectorDetails = new ConnectorDetails(connector, bundleCoordinate, componentLog);
-
- final FrameworkConnectorInitializationContext initContext = createConnectorInitializationContext(managedProcessGroup, componentLog);
-
final Authorizable connectorsAuthorizable = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
@@ -519,6 +495,18 @@ public class ExtensionBuilder {
}
};
+ final Connector connector;
+ try {
+ connector = createConnector();
+ } catch (final Exception e) {
+ logger.error("Could not create Connector of type {} from {} for ID {} due to: {}; creating \"Ghost\" implementation", type, bundleCoordinate, identifier, e.getMessage(), e);
+ return createGhostConnectorNode(connectorsAuthorizable);
+ }
+
+ final String componentType = connector.getClass().getSimpleName();
+ final ComponentLog componentLog = new SimpleProcessLogger(identifier, connector, new StandardLoggingContext());
+ final ConnectorDetails connectorDetails = new ConnectorDetails(connector, bundleCoordinate, componentLog);
+
final ConnectorNode connectorNode = new StandardConnectorNode(
identifier,
flowController.getFlowManager(),
@@ -530,16 +518,58 @@ public class ExtensionBuilder {
activeConfigurationContext,
connectorStateTransition,
flowContextFactory,
- connectorValidationTrigger
+ connectorValidationTrigger,
+ false
);
- initializeDefaultValues(connector, connectorNode.getActiveFlowContext());
- // TODO: If an Exception is thrown in the call to #initialize, we should create a Ghosted Connector
- connectorNode.initializeConnector(initContext);
+ try {
+ initializeDefaultValues(connector, connectorNode.getActiveFlowContext());
+
+ final FrameworkConnectorInitializationContext initContext = createConnectorInitializationContext(managedProcessGroup, componentLog);
+ connectorNode.initializeConnector(initContext);
+ } catch (final Exception e) {
+ logger.error("Could not initialize Connector of type {} from {} for ID {}; creating \"Ghost\" implementation", type, bundleCoordinate, identifier, e);
+ return createGhostConnectorNode(connectorsAuthorizable);
+ }
+
+ if (loadInitialFlow) {
+ try {
+ connectorNode.loadInitialFlow();
+ } catch (final Exception e) {
+ logger.error("Failed to load initial flow for {}; creating \"Ghost\" implementation", connectorNode, e);
+ return createGhostConnectorNode(connectorsAuthorizable);
+ }
+ }
return connectorNode;
}
+ private ConnectorNode createGhostConnectorNode(final Authorizable connectorsAuthorizable) {
+ final GhostConnector ghostConnector = new GhostConnector(identifier, type);
+ final String simpleClassName = type.contains(".") ? StringUtils.substringAfterLast(type, ".") : type;
+ final String componentType = "(Missing) " + simpleClassName;
+ final ComponentLog componentLog = new SimpleProcessLogger(identifier, ghostConnector, new StandardLoggingContext());
+ final ConnectorDetails connectorDetails = new ConnectorDetails(ghostConnector, bundleCoordinate, componentLog);
+
+ // If an instance class loader has been created for this connector, remove it because it's no longer necessary.
+ extensionManager.removeInstanceClassLoader(identifier);
+
+ return new StandardConnectorNode(
+ identifier,
+ flowController.getFlowManager(),
+ extensionManager,
+ connectorsAuthorizable,
+ connectorDetails,
+ componentType,
+ type,
+ activeConfigurationContext,
+ connectorStateTransition,
+ flowContextFactory,
+ connectorValidationTrigger,
+ true
+ );
+ }
+
private void initializeDefaultValues(final Connector connector, final FrameworkFlowContext flowContext) {
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connector.getClass(), identifier)) {
final List<ConfigurationStep> configSteps = connector.getConfigurationSteps();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 65e8011b90..96bbd8f4f7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -779,16 +779,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
.connectorStateTransition(stateTransition)
.connectorInitializationContextBuilder(flowController.getConnectorRepository().createInitializationContextBuilder())
.connectorValidationTrigger(flowController.getConnectorValidationTrigger())
- .buildConnector();
-
- if (firstTimeAdded) {
- try {
- connectorNode.loadInitialFlow();
- } catch (final Exception e) {
- logger.error("Failed to load initial flow for Connector {}", connectorNode, e);
- // TODO: Create a Ghosted Connector instead
- }
- }
+ .buildConnector(firstTimeAdded);
// Establish the Connector as the parent authorizable of the managed root group
managedRootGroup.setExplicitParentAuthorizable(connectorNode);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index 03da830e03..a1490409da 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -513,7 +513,8 @@ public class TestStandardConnectorNode {
new StandardConnectorConfigurationContext(assetManager, secretsManager),
stateTransition,
flowContextFactory,
- validationTrigger);
+ validationTrigger,
+ false);
// mock secrets manager
final SecretsManager secretsManager = mock(SecretsManager.class);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 1fa240ced1..9e60291f13 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -66,14 +66,9 @@ import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.PropertyDependency;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationResult;
-import org.apache.nifi.components.connector.ConnectorAssetRepository;
-import org.apache.nifi.components.state.Scope;
-import org.apache.nifi.components.state.StateMap;
-import org.apache.nifi.components.validation.ValidationState;
-import org.apache.nifi.components.validation.ValidationStatus;
-import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorAssetRepository;
import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
@@ -85,6 +80,11 @@ import org.apache.nifi.components.connector.Secret;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateMap;
+import org.apache.nifi.components.validation.ValidationState;
+import org.apache.nifi.components.validation.ValidationStatus;
+import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.connectable.Funnel;
@@ -144,8 +144,8 @@ import org.apache.nifi.groups.ProcessGroupCounts;
import org.apache.nifi.groups.RemoteProcessGroup;
import org.apache.nifi.groups.RemoteProcessGroupCounts;
import org.apache.nifi.history.History;
-import org.apache.nifi.nar.ExtensionDefinition;
import org.apache.nifi.manifest.RuntimeManifestService;
+import org.apache.nifi.nar.ExtensionDefinition;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarClassLoadersHolder;
import org.apache.nifi.nar.NarManifest;
@@ -5256,9 +5256,8 @@ public final class DtoFactory {
final ValidationState validationState = connector.getValidationState();
dto.setValidationStatus(validationState.getStatus().name());
dto.setValidationErrors(convertValidationErrors(validationState.getValidationErrors()));
-
- final String canonicalName = connector.getConnector().getClass().getCanonicalName();
- dto.setType(canonicalName != null ? canonicalName : connector.getConnector().getClass().getName());
+ dto.setType(connector.getCanonicalClassName());
+ dto.setExtensionMissing(connector.isExtensionMissing());
dto.setBundle(createBundleDto(connector.getBundleCoordinate()));
dto.setState(connector.getCurrentState().name());
(nifi) 33/40: NIFI-15453: Adding an endpoint to return all controller services for … (#10770)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit b7420f7535db880e06b3ddc2823cc734572d659b
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Thu Jan 15 14:19:10 2026 -0500
NIFI-15453: Adding an endpoint to return all controller services for … (#10770)
* NIFI-15453: Adding an endpoint to return all controller services for a process group in a connector.
* NIFI-15453: Addressing review feedback.
This closes #10770
---
.../org/apache/nifi/web/NiFiServiceFacade.java | 3 +
.../apache/nifi/web/StandardNiFiServiceFacade.java | 22 ++++++
.../org/apache/nifi/web/api/ConnectorResource.java | 79 ++++++++++++++++++++++
.../apache/nifi/web/api/TestConnectorResource.java | 34 ++++++++++
4 files changed, 138 insertions(+)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 09761a5076..fba789287f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -224,6 +224,9 @@ public interface NiFiServiceFacade {
ProcessGroupStatusEntity getConnectorProcessGroupStatus(String id, Boolean recursive);
+ Set<ControllerServiceEntity> getConnectorControllerServices(String connectorId, String processGroupId, boolean includeAncestorGroups,
+ boolean includeDescendantGroups, boolean includeReferencingComponents);
+
void verifyCanVerifyConnectorConfigurationStep(String connectorId, String configurationStepName);
List<ConfigVerificationResultDTO> performConnectorConfigurationStepVerification(String connectorId, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 195aca6e21..ae5e9ae906 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -3721,6 +3721,28 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return entityFactory.createProcessGroupStatusEntity(dto, permissions);
}
+ @Override
+ public Set<ControllerServiceEntity> getConnectorControllerServices(final String connectorId, final String processGroupId,
+ final boolean includeAncestorGroups, final boolean includeDescendantGroups, final boolean includeReferencingComponents) {
+ final ConnectorNode connectorNode = connectorDAO.getConnector(connectorId);
+ final ProcessGroup managedProcessGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
+ final ProcessGroup targetProcessGroup = managedProcessGroup.findProcessGroup(processGroupId);
+ if (targetProcessGroup == null) {
+ throw new ResourceNotFoundException("Process Group with ID " + processGroupId + " was not found within Connector " + connectorId);
+ }
+
+ final Set<ControllerServiceNode> serviceNodes = new HashSet<>();
+ serviceNodes.addAll(targetProcessGroup.getControllerServices(includeAncestorGroups));
+
+ if (includeDescendantGroups) {
+ serviceNodes.addAll(targetProcessGroup.findAllControllerServices());
+ }
+
+ return serviceNodes.stream()
+ .map(serviceNode -> createControllerServiceEntity(serviceNode, includeReferencingComponents))
+ .collect(Collectors.toSet());
+ }
+
@Override
public void verifyCanVerifyConnectorConfigurationStep(final String connectorId, final String configurationStepName) {
connectorDAO.verifyCanVerifyConfigurationStep(connectorId, configurationStepName);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
index c2f7b4cddf..cb5146afdb 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
@@ -86,6 +86,8 @@ import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.SearchResultsEntity;
@@ -104,6 +106,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -128,6 +131,7 @@ public class ConnectorResource extends ApplicationResource {
private NiFiServiceFacade serviceFacade;
private Authorizer authorizer;
private FlowResource flowResource;
+ private ControllerServiceResource controllerServiceResource;
private UploadRequestReplicator uploadRequestReplicator;
private final RequestManager<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>> configVerificationRequestManager =
@@ -1411,6 +1415,76 @@ public class ConnectorResource extends ApplicationResource {
return generateOkResponse(entity).build();
}
+ /**
+ * Retrieves all the controller services in the specified process group within a connector.
+ *
+ * @param connectorId The id of the connector
+ * @param processGroupId The process group id within the connector's hierarchy
+ * @param includeAncestorGroups Whether to include ancestor process groups
+ * @param includeDescendantGroups Whether to include descendant process groups
+ * @param includeReferences Whether to include services' referencing components in the response
+ * @return A controllerServicesEntity.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{connectorId}/flow/process-groups/{processGroupId}/controller-services")
+ @Operation(
+ summary = "Gets all controller services for a process group within a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ControllerServicesEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ },
+ description = "Returns the controller services for the specified process group within the connector's hierarchy. The processGroupId can be " +
+ "obtained from the managedProcessGroupId field of the ConnectorDTO for the root process group, or from child process " +
+ "groups within the flow."
+ )
+ public Response getControllerServicesFromConnectorProcessGroup(
+ @Parameter(description = "The connector id.", required = true)
+ @PathParam("connectorId") final String connectorId,
+ @Parameter(description = "The process group id.", required = true)
+ @PathParam("processGroupId") final String processGroupId,
+ @Parameter(description = "Whether or not to include parent/ancestor process groups")
+ @QueryParam("includeAncestorGroups")
+ @DefaultValue("true") final boolean includeAncestorGroups,
+ @Parameter(description = "Whether or not to include descendant process groups")
+ @QueryParam("includeDescendantGroups")
+ @DefaultValue("false") final boolean includeDescendantGroups,
+ @Parameter(description = "Whether or not to include services' referencing components in the response")
+ @QueryParam("includeReferencingComponents")
+ @DefaultValue("true") final boolean includeReferences) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // authorize access to the connector
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(connectorId);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ // get the controller services for the specified process group within the connector's hierarchy
+ final Set<ControllerServiceEntity> controllerServices = serviceFacade.getConnectorControllerServices(
+ connectorId, processGroupId, includeAncestorGroups, includeDescendantGroups, includeReferences);
+ controllerServiceResource.populateRemainingControllerServiceEntitiesContent(controllerServices);
+
+ // create the response entity
+ final ControllerServicesEntity entity = new ControllerServicesEntity();
+ entity.setCurrentTime(new Date());
+ entity.setControllerServices(controllerServices);
+
+ // generate the response
+ return generateOkResponse(entity).build();
+ }
+
/**
* Retrieves the status for the process group managed by the specified connector.
*
@@ -1710,6 +1784,11 @@ public class ConnectorResource extends ApplicationResource {
this.flowResource = flowResource;
}
+ @Autowired
+ public void setControllerServiceResource(final ControllerServiceResource controllerServiceResource) {
+ this.controllerServiceResource = controllerServiceResource;
+ }
+
@Autowired(required = false)
public void setUploadRequestReplicator(final UploadRequestReplicator uploadRequestReplicator) {
this.uploadRequestReplicator = uploadRequestReplicator;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
index 5c22f82fd1..c218d87d68 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
@@ -36,6 +36,8 @@ import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.apache.nifi.web.api.entity.ControllerServicesEntity;
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
@@ -49,6 +51,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.net.URI;
import java.util.List;
+import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -91,6 +94,9 @@ public class TestConnectorResource {
@Mock
private FlowResource flowResource;
+ @Mock
+ private ControllerServiceResource controllerServiceResource;
+
private static final String CONNECTOR_ID = "test-connector-id";
private static final String CONNECTOR_NAME = "Test Connector";
private static final String CONNECTOR_TYPE = "TestConnectorType";
@@ -116,6 +122,7 @@ public class TestConnectorResource {
connectorResource.setServiceFacade(serviceFacade);
connectorResource.setFlowResource(flowResource);
+ connectorResource.setControllerServiceResource(controllerServiceResource);
connectorResource.httpServletRequest = httpServletRequest;
connectorResource.properties = properties;
connectorResource.uriInfo = uriInfo;
@@ -384,6 +391,33 @@ public class TestConnectorResource {
verify(serviceFacade, never()).getConnectorFlow(anyString(), anyString(), eq(false));
}
+ @Test
+ public void testGetControllerServicesFromConnectorProcessGroup() {
+ final Set<ControllerServiceEntity> controllerServices = Set.of();
+ when(serviceFacade.getConnectorControllerServices(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true)).thenReturn(controllerServices);
+
+ try (Response response = connectorResource.getControllerServicesFromConnectorProcessGroup(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true)) {
+ assertEquals(200, response.getStatus());
+ final ControllerServicesEntity entity = (ControllerServicesEntity) response.getEntity();
+ assertEquals(controllerServices, entity.getControllerServices());
+ }
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade).getConnectorControllerServices(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true);
+ verify(controllerServiceResource).populateRemainingControllerServiceEntitiesContent(controllerServices);
+ }
+
+ @Test
+ public void testGetControllerServicesFromConnectorProcessGroupNotAuthorized() {
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () ->
+ connectorResource.getControllerServicesFromConnectorProcessGroup(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true));
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade, never()).getConnectorControllerServices(anyString(), anyString(), eq(true), eq(false), eq(true));
+ }
+
private ConnectorEntity createConnectorEntity() {
final ConnectorEntity entity = new ConnectorEntity();
(nifi) 28/40: NIFI-15440: Implementation of ConnectorActions (#10748)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 8332e6db55988866dcd4d96028bab32da7b8d3c7
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Mon Jan 12 14:14:08 2026 -0500
NIFI-15440: Implementation of ConnectorActions (#10748)
* NIFI-15440: Implementation of ConnectorActions
* NIFI-15440: Addressed review feedback
* NIFI-15440: Addressed issues that occurred after rebase
* NIFI-15440: Addressed review feedback
---
.../nifi/web/api/dto/ConnectorActionDTO.java | 70 +++++++++
.../org/apache/nifi/web/api/dto/ConnectorDTO.java | 11 ++
.../nifi/components/connector/ConnectorAction.java | 49 ++++++
.../nifi/components/connector/ConnectorNode.java | 7 +
.../connector/StandardConnectorAction.java | 81 ++++++++++
.../connector/StandardConnectorNode.java | 170 +++++++++++++++++++++
.../StandaloneProcessGroupLifecycle.java | 16 ++
.../connector/StandardConnectorNodeIT.java | 1 +
.../org/apache/nifi/web/api/dto/DtoFactory.java | 17 +++
.../AuthorizingProcessGroupLifecycle.java | 6 +
.../tests/system/DataQueuingConnector.java | 69 +++++++--
11 files changed, 483 insertions(+), 14 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorActionDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorActionDTO.java
new file mode 100644
index 0000000000..97e27fbfcb
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorActionDTO.java
@@ -0,0 +1,70 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+/**
+ * Represents an action that can be performed on a Connector.
+ */
+@XmlType(name = "connectorAction")
+public class ConnectorActionDTO {
+
+ private String name;
+ private String description;
+ private Boolean allowed;
+ private String reasonNotAllowed;
+
+ @Schema(description = "The name of the action.")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "A description of what this action does.")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ @Schema(description = "Whether this action is currently allowed to be performed, based on the state of the Connector. " +
+ "Note that a value of 'true' does not imply that the user has permission to perform the action.")
+ public Boolean getAllowed() {
+ return allowed;
+ }
+
+ public void setAllowed(final Boolean allowed) {
+ this.allowed = allowed;
+ }
+
+ @Schema(description = "The reason why this action is not allowed, or null if the action is allowed.")
+ public String getReasonNotAllowed() {
+ return reasonNotAllowed;
+ }
+
+ public void setReasonNotAllowed(final String reasonNotAllowed) {
+ this.reasonNotAllowed = reasonNotAllowed;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
index d2d08590a3..ee891b10af 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.xml.bind.annotation.XmlType;
import java.util.Collection;
+import java.util.List;
/**
* Component representing a Connector instance.
@@ -41,6 +42,7 @@ public class ConnectorDTO extends ComponentDTO {
private String configurationUrl;
private String detailsUrl;
+ private List<ConnectorActionDTO> availableActions;
@Schema(description = "The name of the Connector.")
public String getName() {
@@ -158,4 +160,13 @@ public class ConnectorDTO extends ComponentDTO {
public void setExtensionMissing(final Boolean extensionMissing) {
this.extensionMissing = extensionMissing;
}
+
+ @Schema(description = "The available actions that can be performed on this Connector.")
+ public List<ConnectorActionDTO> getAvailableActions() {
+ return availableActions;
+ }
+
+ public void setAvailableActions(final List<ConnectorActionDTO> availableActions) {
+ this.availableActions = availableActions;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorAction.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorAction.java
new file mode 100644
index 0000000000..60a4865269
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorAction.java
@@ -0,0 +1,49 @@
+/*
+ * 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.components.connector;
+
+/**
+ * Represents an action that can be performed on a Connector.
+ */
+public interface ConnectorAction {
+
+ /**
+ * Returns the name of this action.
+ * @return the action name
+ */
+ String getName();
+
+ /**
+ * Returns a description of what this action does.
+ * @return the action description
+ */
+ String getDescription();
+
+ /**
+ * Returns whether this action is currently allowed to be performed.
+ * @return true if the action is allowed, false otherwise
+ */
+ boolean isAllowed();
+
+ /**
+ * Returns the reason why this action is not allowed, or null if the action is allowed.
+ * @return the reason the action is not allowed, or null if allowed
+ */
+ String getReasonNotAllowed();
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index 146be70d73..bb3bdf6e2d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -244,4 +244,11 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
void inheritConfiguration(List<VersionedConfigurationStep> activeFlowConfiguration, List<VersionedConfigurationStep> workingFlowConfiguration,
Bundle flowContextBundle) throws FlowUpdateException;
+
+ /**
+ * Returns the list of available actions that can be performed on this Connector.
+ * Each action includes whether it is currently allowed and, if not, the reason why.
+ * @return the list of available actions
+ */
+ List<ConnectorAction> getAvailableActions();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorAction.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorAction.java
new file mode 100644
index 0000000000..3989bbe02c
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorAction.java
@@ -0,0 +1,81 @@
+/*
+ * 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.components.connector;
+
+import java.util.Objects;
+
+public class StandardConnectorAction implements ConnectorAction {
+
+ private final String name;
+ private final String description;
+ private final boolean allowed;
+ private final String reasonNotAllowed;
+
+ public StandardConnectorAction(final String name, final String description, final boolean allowed, final String reasonNotAllowed) {
+ this.name = Objects.requireNonNull(name, "name is required");
+ this.description = Objects.requireNonNull(description, "description is required");
+ this.allowed = allowed;
+ this.reasonNotAllowed = reasonNotAllowed;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public boolean isAllowed() {
+ return allowed;
+ }
+
+ @Override
+ public String getReasonNotAllowed() {
+ return reasonNotAllowed;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ final StandardConnectorAction that = (StandardConnectorAction) other;
+ return allowed == that.allowed
+ && Objects.equals(name, that.name)
+ && Objects.equals(description, that.description)
+ && Objects.equals(reasonNotAllowed, that.reasonNotAllowed);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, description, allowed, reasonNotAllowed);
+ }
+
+ @Override
+ public String toString() {
+ if (reasonNotAllowed == null) {
+ return "StandardConnectorAction[name=" + name + ", allowed=" + allowed + "]";
+ }
+ return "StandardConnectorAction[name=" + name + ", allowed=" + allowed + ", reason=" + reasonNotAllowed + "]";
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 5edd2d3fb1..2bb5793432 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -35,6 +35,7 @@ import org.apache.nifi.components.validation.ValidationState;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.FlowFileActivity;
import org.apache.nifi.connectable.FlowFileTransferCounts;
+import org.apache.nifi.controller.ProcessorNode;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.controller.queue.DropFlowFileStatus;
import org.apache.nifi.controller.queue.QueueSize;
@@ -866,6 +867,175 @@ public class StandardConnectorNode implements ConnectorNode {
recreateWorkingFlowContext();
}
+ @Override
+ public List<ConnectorAction> getAvailableActions() {
+ final List<ConnectorAction> actions = new ArrayList<>();
+ final ConnectorState currentState = getCurrentState();
+ final boolean dataQueued = activeFlowContext.getManagedProcessGroup().isDataQueued();
+ final boolean stopped = isStopped();
+
+ actions.add(createStartAction(stopped));
+ actions.add(createStopAction(currentState));
+ actions.add(createConfigureAction());
+ actions.add(createDiscardWorkingConfigAction());
+ actions.add(createPurgeFlowFilesAction(stopped, dataQueued));
+ actions.add(createDrainFlowFilesAction(stopped, dataQueued));
+ actions.add(createApplyUpdatesAction(currentState));
+ actions.add(createDeleteAction(stopped, dataQueued));
+
+ return actions;
+ }
+
+ private boolean isStopped() {
+ final ConnectorState currentState = getCurrentState();
+ if (currentState == ConnectorState.STOPPED) {
+ return true;
+ }
+ if (currentState == ConnectorState.UPDATED || currentState == ConnectorState.UPDATE_FAILED) {
+ return !hasActiveThread(getActiveFlowContext().getManagedProcessGroup());
+ }
+ return false;
+ }
+
+ private ConnectorAction createStartAction(final boolean stopped) {
+ final boolean allowed;
+ final String reason;
+
+ if (!stopped) {
+ allowed = false;
+ reason = "Connector is not stopped";
+ } else {
+ allowed = true;
+ reason = null;
+ }
+
+ return new StandardConnectorAction("START", "Start the connector", allowed, reason);
+ }
+
+ private ConnectorAction createStopAction(final ConnectorState currentState) {
+ final boolean allowed;
+ if (currentState == ConnectorState.RUNNING || currentState == ConnectorState.STARTING) {
+ allowed = true;
+ } else if (currentState == ConnectorState.UPDATED || currentState == ConnectorState.UPDATE_FAILED) {
+ allowed = hasActiveThread(activeFlowContext.getManagedProcessGroup());
+ } else {
+ allowed = false;
+ }
+
+ final String reason = allowed ? null : "Connector is not running";
+ return new StandardConnectorAction("STOP", "Stop the connector", allowed, reason);
+ }
+
+ private ConnectorAction createConfigureAction() {
+ return new StandardConnectorAction("CONFIGURE", "Configure the connector", true, null);
+ }
+
+ private ConnectorAction createDiscardWorkingConfigAction() {
+ final boolean allowed = hasWorkingConfigurationChanges();
+ final String reason = allowed ? null : "No pending changes to discard";
+
+ return new StandardConnectorAction("DISCARD_WORKING_CONFIGURATION", "Discard any changes made to the working configuration", allowed, reason);
+ }
+
+ private boolean hasActiveThread(final ProcessGroup group) {
+ for (final ProcessorNode processor : group.getProcessors()) {
+ if (processor.getActiveThreadCount() > 0) {
+ return true;
+ }
+ }
+
+ for (final ProcessGroup childGroup : group.getProcessGroups()) {
+ if (hasActiveThread(childGroup)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private ConnectorAction createPurgeFlowFilesAction(final boolean stopped, final boolean dataQueued) {
+ final boolean allowed;
+ final String reason;
+
+ if (!stopped) {
+ allowed = false;
+ reason = "Connector must be stopped";
+ } else if (!dataQueued) {
+ allowed = false;
+ reason = "No data is queued";
+ } else {
+ allowed = true;
+ reason = null;
+ }
+
+ return new StandardConnectorAction("PURGE_FLOWFILES", "Purge all FlowFiles from the connector, dropping all data without processing it", allowed, reason);
+ }
+
+ private ConnectorAction createDrainFlowFilesAction(final boolean stopped, final boolean dataQueued) {
+ final boolean allowed;
+ final String reason;
+
+ if (!stopped) {
+ allowed = false;
+ reason = "Connector must be stopped";
+ } else if (!dataQueued) {
+ allowed = false;
+ reason = "No data is queued";
+ } else {
+ allowed = true;
+ reason = null;
+ }
+
+ return new StandardConnectorAction("DRAIN_FLOWFILES", "Process data that is currently in the flow but do not ingest any additional data", allowed, reason);
+ }
+
+ private ConnectorAction createApplyUpdatesAction(final ConnectorState currentState) {
+ final boolean allowed;
+ final String reason;
+
+ if (currentState == ConnectorState.PREPARING_FOR_UPDATE || currentState == ConnectorState.UPDATING) {
+ allowed = false;
+ reason = "Connector is updating";
+ } else if (!hasWorkingConfigurationChanges()) {
+ allowed = false;
+ reason = "No pending changes";
+ } else {
+ allowed = true;
+ reason = null;
+ }
+
+ return new StandardConnectorAction("APPLY_UPDATES", "Apply the working configuration to the active configuration", allowed, reason);
+ }
+
+ private ConnectorAction createDeleteAction(final boolean stopped, final boolean dataQueued) {
+ final boolean allowed;
+ final String reason;
+
+ if (!stopped) {
+ allowed = false;
+ reason = "Connector must be stopped";
+ } else if (dataQueued) {
+ allowed = false;
+ reason = "Data is queued";
+ } else {
+ allowed = true;
+ reason = null;
+ }
+
+ return new StandardConnectorAction("DELETE", "Delete the connector", allowed, reason);
+ }
+
+ private boolean hasWorkingConfigurationChanges() {
+ final FrameworkFlowContext workingContext = this.workingFlowContext;
+ if (workingContext == null) {
+ return false;
+ }
+
+ final ConnectorConfiguration activeConfig = activeFlowContext.getConfigurationContext().toConnectorConfiguration();
+ final ConnectorConfiguration workingConfig = workingContext.getConfigurationContext().toConnectorConfiguration();
+ return !Objects.equals(activeConfig, workingConfig);
+ }
+
@Override
public Authorizable getParentAuthorizable() {
return parentAuthorizable;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneProcessGroupLifecycle.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneProcessGroupLifecycle.java
index 29d5baa8b3..95e7f8995d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneProcessGroupLifecycle.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneProcessGroupLifecycle.java
@@ -291,4 +291,20 @@ public class StandaloneProcessGroupLifecycle implements ProcessGroupLifecycle {
return CompletableFuture.allOf(stopFutures.toArray(new CompletableFuture[0]));
}
+
+ @Override
+ public int getActiveThreadCount() {
+ return getActiveThreadCount(processGroup);
+ }
+
+ private int getActiveThreadCount(final ProcessGroup group) {
+ int total = 0;
+ for (final ProcessorNode processor : group.getProcessors()) {
+ total += processor.getActiveThreadCount();
+ }
+ for (final ProcessGroup childGroup : group.getProcessGroups()) {
+ total += getActiveThreadCount(childGroup);
+ }
+ return total;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
index f8e63e2723..336d5a20fb 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
@@ -159,6 +159,7 @@ public class StandardConnectorNodeIT {
when(flowController.getFlowFileEventRepository()).thenReturn(mock(FlowFileEventRepository.class));
when(flowController.getConnectorRepository()).thenReturn(connectorRepository);
when(flowController.getValidationTrigger()).thenReturn(mock(ValidationTrigger.class));
+ when(flowController.getConnectorValidationTrigger()).thenReturn(mock(ConnectorValidationTrigger.class));
doAnswer(invocation -> {
return createConnection(invocation.getArgument(0), invocation.getArgument(1), invocation.getArgument(2), invocation.getArgument(3), invocation.getArgument(4));
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 9e60291f13..652cf93ffd 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -68,6 +68,7 @@ import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorAction;
import org.apache.nifi.components.connector.ConnectorAssetRepository;
import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
@@ -5266,10 +5267,26 @@ public final class DtoFactory {
dto.setManagedProcessGroupId(activeFlowContext.getManagedProcessGroup().getIdentifier());
dto.setActiveConfiguration(createConnectorConfigurationDtoFromFlowContext(connector, activeFlowContext));
dto.setWorkingConfiguration(createConnectorConfigurationDtoFromFlowContext(connector, connector.getWorkingFlowContext()));
+ dto.setAvailableActions(createConnectorActionDtos(connector));
return dto;
}
+ private List<ConnectorActionDTO> createConnectorActionDtos(final ConnectorNode connector) {
+ return connector.getAvailableActions().stream()
+ .map(this::createConnectorActionDto)
+ .collect(Collectors.toList());
+ }
+
+ private ConnectorActionDTO createConnectorActionDto(final ConnectorAction action) {
+ final ConnectorActionDTO dto = new ConnectorActionDTO();
+ dto.setName(action.getName());
+ dto.setDescription(action.getDescription());
+ dto.setAllowed(action.isAllowed());
+ dto.setReasonNotAllowed(action.getReasonNotAllowed());
+ return dto;
+ }
+
private ConnectorConfigurationDTO createConnectorConfigurationDtoFromFlowContext(final ConnectorNode connector, final FrameworkFlowContext flowContext) {
final List<ConfigurationStep> configurationSteps = connector.getConfigurationSteps();
if (configurationSteps == null || configurationSteps.isEmpty()) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupLifecycle.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupLifecycle.java
index d29a056425..94f822d724 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupLifecycle.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingProcessGroupLifecycle.java
@@ -84,5 +84,11 @@ public class AuthorizingProcessGroupLifecycle implements ProcessGroupLifecycle {
authContext.authorizeWrite();
return delegate.stopProcessors();
}
+
+ @Override
+ public int getActiveThreadCount() {
+ authContext.authorizeRead();
+ return delegate.getActiveThreadCount();
+ }
}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
index de6701159d..f3503b7ed1 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
@@ -21,14 +21,17 @@ import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.connector.AbstractConnector;
import org.apache.nifi.components.connector.ConfigurationStep;
import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.ConnectableComponent;
import org.apache.nifi.flow.ConnectableComponentType;
+import org.apache.nifi.flow.Position;
import org.apache.nifi.flow.ScheduledState;
import org.apache.nifi.flow.VersionedConnection;
import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.flow.VersionedProcessor;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -41,19 +44,16 @@ public class DataQueuingConnector extends AbstractConnector {
@Override
public VersionedExternalFlow getInitialFlow() {
- final VersionedProcessor generate = new VersionedProcessor();
- generate.setName("GenerateFlowFile");
- generate.setType("org.apache.nifi.processors.tests.system.GenerateFlowFile");
- generate.setIdentifier("gen-1");
- generate.setGroupIdentifier("1234");
- generate.setProperties(Map.of("File Size", "1 KB"));
-
- final VersionedProcessor terminate = new VersionedProcessor();
- terminate.setName("TerminateFlowFile");
- terminate.setType("org.apache.nifi.processors.tests.system.TerminateFlowFile");
- terminate.setIdentifier("term-1");
- terminate.setGroupIdentifier("1234");
- terminate.setScheduledState(ScheduledState.DISABLED);
+ final Bundle bundle = new Bundle();
+ bundle.setGroup("org.apache.nifi");
+ bundle.setArtifact("nifi-system-test-extensions-nar");
+ bundle.setVersion("2.7.0-SNAPSHOT");
+
+ final VersionedProcessor generate = createVersionedProcessor("gen-1", "1234", "GenerateFlowFile",
+ "org.apache.nifi.processors.tests.system.GenerateFlowFile", bundle, Map.of("File Size", "1 KB"), ScheduledState.RUNNING);
+
+ final VersionedProcessor terminate = createVersionedProcessor("term-1", "1234", "TerminateFlowFile",
+ "org.apache.nifi.processors.tests.system.TerminateFlowFile", bundle, Collections.emptyMap(), ScheduledState.DISABLED);
final ConnectableComponent source = new ConnectableComponent();
source.setId(generate.getIdentifier());
@@ -66,10 +66,18 @@ public class DataQueuingConnector extends AbstractConnector {
destination.setGroupId("1234");
final VersionedConnection connection = new VersionedConnection();
+ connection.setIdentifier("generate-to-terminate-1");
connection.setSource(source);
connection.setDestination(destination);
connection.setGroupIdentifier("1234");
- connection.setIdentifier("generate-to-terminate-1");
+ connection.setSelectedRelationships(Set.of("success"));
+ connection.setBackPressureDataSizeThreshold("1 GB");
+ connection.setBackPressureObjectThreshold(10_000L);
+ connection.setBends(Collections.emptyList());
+ connection.setLabelIndex(1);
+ connection.setFlowFileExpiration("0 sec");
+ connection.setPrioritizers(Collections.emptyList());
+ connection.setzIndex(1L);
final VersionedProcessGroup rootGroup = new VersionedProcessGroup();
rootGroup.setName("Data Queuing Connector");
@@ -79,6 +87,7 @@ public class DataQueuingConnector extends AbstractConnector {
final VersionedExternalFlow flow = new VersionedExternalFlow();
flow.setFlowContents(rootGroup);
+ flow.setParameterContexts(Collections.emptyMap());
return flow;
}
@@ -95,4 +104,36 @@ public class DataQueuingConnector extends AbstractConnector {
@Override
public void applyUpdate(final FlowContext workingFlowContext, final FlowContext activeFlowContext) {
}
+
+ private VersionedProcessor createVersionedProcessor(final String identifier, final String groupIdentifier, final String name,
+ final String type, final Bundle bundle, final Map<String, String> properties,
+ final ScheduledState scheduledState) {
+ final VersionedProcessor processor = new VersionedProcessor();
+ processor.setIdentifier(identifier);
+ processor.setGroupIdentifier(groupIdentifier);
+ processor.setName(name);
+ processor.setType(type);
+ processor.setBundle(bundle);
+ processor.setProperties(properties);
+ processor.setPropertyDescriptors(Collections.emptyMap());
+ processor.setScheduledState(scheduledState);
+
+ processor.setBulletinLevel("WARN");
+ processor.setSchedulingStrategy("TIMER_DRIVEN");
+ processor.setSchedulingPeriod("0 sec");
+ processor.setExecutionNode("ALL");
+ processor.setConcurrentlySchedulableTaskCount(1);
+ processor.setPenaltyDuration("30 sec");
+ processor.setYieldDuration("1 sec");
+ processor.setRunDurationMillis(0L);
+ processor.setPosition(new Position(0, 0));
+
+ processor.setAutoTerminatedRelationships(Collections.emptySet());
+ processor.setRetryCount(10);
+ processor.setRetriedRelationships(Collections.emptySet());
+ processor.setBackoffMechanism("PENALIZE_FLOWFILE");
+ processor.setMaxBackoffPeriod("10 mins");
+
+ return processor;
+ }
}
(nifi) 38/40: NIFI-15258: Fixes necessary after rebasing against main
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 109faf50a0c5f4413a6649fa52366964433d79fe
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Wed Jan 21 15:46:57 2026 -0500
NIFI-15258: Fixes necessary after rebasing against main
---
nifi-assembly/pom.xml | 2 +-
nifi-commons/nifi-connector-utils/pom.xml | 2 +-
.../nifi-connector-mock-api/pom.xml | 4 +-
.../nifi-connector-mock-server-nar/pom.xml | 6 +--
.../nifi-connector-mock-server/pom.xml | 12 +++---
.../nifi-connector-mock-integration-tests/pom.xml | 18 ++++-----
.../pom.xml | 4 +-
.../nifi-connector-mock-test-connectors/pom.xml | 4 +-
.../nifi-connector-mock-test-bundle/pom.xml | 2 +-
.../nifi-connector-mock/pom.xml | 24 ++++++------
nifi-connector-mock-bundle/pom.xml | 2 +-
.../nifi-kafka-to-s3-connector/pom.xml | 2 +-
.../src/main/resources/flows/Kafka_to_S3.json | 44 +++++++++++-----------
.../nifi-kafka-to-s3-integration-tests/pom.xml | 36 +++++++++---------
.../nifi-kafka-to-s3-nar/pom.xml | 4 +-
nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml | 2 +-
nifi-connectors/pom.xml | 6 +--
.../kafka/service/Kafka3ConnectionService.java | 3 ++
.../nifi-framework/nifi-framework-core/pom.xml | 3 +-
.../configuration/FlowControllerConfiguration.java | 2 +-
.../VersionedFlowSynchronizerTest.java | 6 +++
.../org/apache/nifi/web/api/TestFlowResource.java | 2 +-
.../tests/system/CalculateConnector.java | 2 +-
.../tests/system/DataQueuingConnector.java | 2 +-
24 files changed, 101 insertions(+), 93 deletions(-)
diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index 1ddf8ea5b5..9933c50316 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -918,7 +918,7 @@ language governing permissions and limitations under the License. -->
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-to-s3-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<!-- AspectJ library needed by the Java Agent used for native library loading (see bootstrap.conf) -->
diff --git a/nifi-commons/nifi-connector-utils/pom.xml b/nifi-commons/nifi-connector-utils/pom.xml
index 2794ecbfd6..8e8b3933f7 100644
--- a/nifi-commons/nifi-connector-utils/pom.xml
+++ b/nifi-commons/nifi-connector-utils/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-commons</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-utils</artifactId>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-api/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-api/pom.xml
index 2d581f17da..f64471bd62 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-api/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock-api</artifactId>
@@ -28,7 +28,7 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-server-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
</dependencies>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server-nar/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-server-nar/pom.xml
index eb4b1e8f72..a5b30a52ec 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server-nar/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server-nar/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock-server-nar</artifactId>
@@ -28,12 +28,12 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-server</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
</dependencies>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml
index 040596e620..4643943e29 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock-server</artifactId>
@@ -36,29 +36,29 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-core-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-core</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-property-encryptor</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-server-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
index 959ac55052..7b092337a4 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-test-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock-integration-tests</artifactId>
@@ -36,12 +36,12 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<!-- Test Dependencies -->
@@ -55,13 +55,13 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-server-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-test-connectors-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
@@ -69,25 +69,25 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-standard-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-standard-services-api-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-standard-shared-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-update-attribute-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
</dependencies>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors-nar/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors-nar/pom.xml
index 89d3a10121..4581c4af0b 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors-nar/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors-nar/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-test-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock-test-connectors-nar</artifactId>
@@ -28,7 +28,7 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-test-connectors</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/pom.xml
index 9f2a86cd96..2632c75566 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-test-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock-test-connectors</artifactId>
@@ -32,7 +32,7 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-utils</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/pom.xml
index c7b734eb41..20805bacfe 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock-test-bundle</artifactId>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock/pom.xml
index 3f6ea8b8cd..740a263a71 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock/pom.xml
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock</artifactId>
@@ -36,25 +36,25 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-nar-utils</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-properties</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<!-- Dependencies necessary to bootstrap NiFi -->
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-nar-utils</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
@@ -62,22 +62,22 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-property-utils</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-python-framework-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-stateless-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<!-- Logging dependencies to ensure that everything funnels through SLF4J -->
@@ -105,19 +105,19 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-jetty-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-provenance-repository-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
</dependencies>
diff --git a/nifi-connector-mock-bundle/pom.xml b/nifi-connector-mock-bundle/pom.xml
index adb016336e..cae0a25dc3 100644
--- a/nifi-connector-mock-bundle/pom.xml
+++ b/nifi-connector-mock-bundle/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connector-mock-bundle</artifactId>
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/pom.xml
index 7f95903475..2ba42137da 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/pom.xml
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-to-s3-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-kafka-to-s3-connector</artifactId>
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/flows/Kafka_to_S3.json b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/flows/Kafka_to_S3.json
index 5df6da47ae..8cfaebc689 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/flows/Kafka_to_S3.json
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/flows/Kafka_to_S3.json
@@ -24,7 +24,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-standard-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"max-bin-age": "#{Merge Latency}",
@@ -154,7 +154,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-kafka-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"Topics": "#{Topic Names}",
@@ -345,7 +345,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-aws-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"FullControl User List": "${s3.permissions.full.users}",
@@ -758,7 +758,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-record-serialization-services-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"Schema Branch": null,
@@ -898,7 +898,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-standard-services-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
@@ -916,7 +916,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-record-serialization-services-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"Schema Branch": null,
@@ -1032,7 +1032,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-standard-services-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
@@ -1050,7 +1050,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-record-serialization-services-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"Schema Branch": null,
@@ -1190,7 +1190,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-standard-services-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
@@ -1208,7 +1208,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-record-serialization-services-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"Schema Branch": null,
@@ -1284,7 +1284,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-standard-services-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
@@ -1302,7 +1302,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-aws-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"Assume Role STS Region": "us-west-2",
@@ -1463,7 +1463,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-aws-service-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
},
{
@@ -1471,7 +1471,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-aws-service-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
@@ -1489,7 +1489,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-record-serialization-services-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"Schema Branch": null,
@@ -1653,7 +1653,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-standard-services-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
@@ -1671,7 +1671,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-confluent-platform-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"SSL Context Service": null,
@@ -1747,7 +1747,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-standard-services-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
@@ -1765,7 +1765,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-confluent-platform-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {},
"propertyDescriptors": {},
@@ -1775,7 +1775,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-standard-services-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
@@ -1793,7 +1793,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-kafka-3-service-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
},
"properties": {
"ack.wait.time": "10 sec",
@@ -1917,7 +1917,7 @@
"bundle": {
"group": "org.apache.nifi",
"artifact": "nifi-kafka-service-api-nar",
- "version": "2.7.0-SNAPSHOT"
+ "version": "2.8.0-SNAPSHOT"
}
}
],
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml
index 345f9b1a38..b7e63e618d 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml
@@ -18,7 +18,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-to-s3-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-kafka-to-s3-integration-tests</artifactId>
@@ -42,12 +42,12 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-api</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
<!-- Test Containers for Kafka and S3 -->
@@ -122,67 +122,67 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-to-s3-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-3-service-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-service-api-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-aws-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-aws-service-api-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-standard-services-api-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-standard-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-standard-shared-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-record-serialization-services-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-confluent-platform-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
@@ -190,25 +190,25 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-jetty-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-provenance-repository-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-mock-server-nar</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
</dependencies>
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/pom.xml
index 76e6e97097..6f7f334eab 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/pom.xml
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-to-s3-bundle</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-kafka-to-s3-nar</artifactId>
@@ -30,7 +30,7 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-kafka-to-s3-connector</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml
index 0fbed3c9ea..71f8649563 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connectors</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-kafka-to-s3-bundle</artifactId>
diff --git a/nifi-connectors/pom.xml b/nifi-connectors/pom.xml
index 4186b8904d..d0a4d3c9b9 100644
--- a/nifi-connectors/pom.xml
+++ b/nifi-connectors/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-connectors</artifactId>
@@ -35,8 +35,8 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-utils</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
-</project>
\ No newline at end of file
+</project>
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
index 9ecf11b6f7..c7cc4782ff 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
@@ -16,7 +16,9 @@
*/
package org.apache.nifi.kafka.service;
+import org.apache.kafka.clients.admin.Admin;
import org.apache.kafka.clients.admin.AdminClientConfig;
+import org.apache.kafka.clients.admin.ListTopicsResult;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.KafkaConsumer;
@@ -62,6 +64,7 @@ import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.ssl.SSLContextService;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
index 7a3e6a98ac..479567966e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
@@ -128,7 +128,6 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-expression-language</artifactId>
<version>2.8.0-SNAPSHOT</version>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
@@ -293,7 +292,7 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-connector-utils</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
index cd17fc0349..63c4f11630 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/framework/configuration/FlowControllerConfiguration.java
@@ -468,7 +468,7 @@ public class FlowControllerConfiguration {
@Bean
public AssetSynchronizer parameterContextAssetSynchronizer() throws Exception {
- return new StandardAssetSynchronizer(flowController(), clusterCoordinator, webClientService(), properties);
+ return new StandardAssetSynchronizer(flowController(), clusterCoordinator, webClientService(), properties, affectedComponentManager());
}
/**
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java
index b01317c83e..f526e25cb4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java
@@ -18,6 +18,7 @@ package org.apache.nifi.controller.serialization;
import org.apache.nifi.cluster.protocol.DataFlow;
import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.ReportingTaskNode;
import org.apache.nifi.controller.SnippetManager;
@@ -51,6 +52,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -278,5 +280,9 @@ class VersionedFlowSynchronizerTest {
when(flowController.getEncryptor()).thenReturn(encryptor);
when(flowController.createVersionedComponentStateLookup(any())).thenReturn(stateLookup);
when(flowController.getControllerServiceProvider()).thenReturn(controllerServiceProvider);
+
+ final ConnectorRepository connectorRepository = mock(ConnectorRepository.class);
+ when(connectorRepository.getConnectors()).thenReturn(Collections.emptyList());
+ when(flowController.getConnectorRepository()).thenReturn(connectorRepository);
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestFlowResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestFlowResource.java
index ea0d3c4fa7..e67680bdb2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestFlowResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestFlowResource.java
@@ -545,7 +545,7 @@ public class TestFlowResource {
entity.setFromTimestamp(Instant.now());
// components is null, so clearBulletins will gather all authorized components
- when(niFiProperties.isNode()).thenReturn(false);
+ when(properties.isNode()).thenReturn(false);
resource.httpServletRequest = new MockHttpServletRequest();
// Capture the function passed to filterComponents (called 3 times)
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java
index d8763e0587..f3fb33d80b 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java
@@ -170,7 +170,7 @@ public class CalculateConnector extends AbstractConnector {
final Bundle bundle = new Bundle();
bundle.setGroup("org.apache.nifi");
bundle.setArtifact("nifi-system-test-extensions-nar");
- bundle.setVersion("2.7.0-SNAPSHOT");
+ bundle.setVersion("2.8.0-SNAPSHOT");
final VersionedProcessor processor = new VersionedProcessor();
processor.setIdentifier("calculate-processor-id");
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
index f3503b7ed1..920c61480a 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
@@ -47,7 +47,7 @@ public class DataQueuingConnector extends AbstractConnector {
final Bundle bundle = new Bundle();
bundle.setGroup("org.apache.nifi");
bundle.setArtifact("nifi-system-test-extensions-nar");
- bundle.setVersion("2.7.0-SNAPSHOT");
+ bundle.setVersion("2.8.0-SNAPSHOT");
final VersionedProcessor generate = createVersionedProcessor("gen-1", "1234", "GenerateFlowFile",
"org.apache.nifi.processors.tests.system.GenerateFlowFile", bundle, Map.of("File Size", "1 KB"), ScheduledState.RUNNING);
(nifi) 36/40: NIFI-15451: Added ability for Connectors to retrieve bundles availabl… (#10756)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 7a288a57bfc522f2b68562d2052dee44ccab324c
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Wed Jan 21 12:25:58 2026 -0500
NIFI-15451: Added ability for Connectors to retrieve bundles availabl… (#10756)
* NIFI-15451: Added ability for Connectors to retrieve bundles available for component types and updated VersionedFlowUtils to make use of it to easily update Versioned components
* NIFI-15451: Added additional unit tests to VersionedFlowUtils; updates to how we compare component versions
* NIFI-15451: Fixed PMD violation
---
.../connector/util/VersionedFlowUtils.java | 212 ++++++++++++
.../connector/util/TestVersionedFlowUtils.java | 368 +++++++++++++++++++++
.../server/MockConnectorInitializationContext.java | 19 +-
...eworkConnectorInitializationContextBuilder.java | 2 +
.../connector/StandardComponentBundleLookup.java | 45 +++
.../StandardConnectorInitializationContext.java | 15 +-
.../apache/nifi/controller/ExtensionBuilder.java | 11 +-
.../nifi/controller/flow/StandardFlowManager.java | 4 +
.../tests/system/CalculateConnector.java | 3 -
9 files changed, 672 insertions(+), 7 deletions(-)
diff --git a/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java b/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java
index 8eb839a3ba..62c42e107b 100644
--- a/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java
+++ b/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java
@@ -19,12 +19,14 @@ package org.apache.nifi.components.connector.util;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.components.connector.ComponentBundleLookup;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.ComponentType;
import org.apache.nifi.flow.ConnectableComponent;
import org.apache.nifi.flow.ConnectableComponentType;
import org.apache.nifi.flow.Position;
import org.apache.nifi.flow.ScheduledState;
+import org.apache.nifi.flow.VersionedConfigurableExtension;
import org.apache.nifi.flow.VersionedConnection;
import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedExternalFlow;
@@ -363,4 +365,214 @@ public class VersionedFlowUtils {
removeUnreferencedControllerServices(childGroup, referencedServiceIds);
}
}
+
+ /**
+ * Updates all processors and controller services in the given process group (and its child groups)
+ * to use the latest available bundle version. See {@link #getLatest(List)} for details on how
+ * version comparison is performed.
+ *
+ * @param processGroup the process group containing components to update
+ * @param componentBundleLookup the lookup used to find available bundles for each component type
+ */
+ public static void updateToLatestBundles(final VersionedProcessGroup processGroup, final ComponentBundleLookup componentBundleLookup) {
+ for (final VersionedProcessor processor : processGroup.getProcessors()) {
+ updateToLatestBundle(processor, componentBundleLookup);
+ }
+
+ for (final VersionedControllerService service : processGroup.getControllerServices()) {
+ updateToLatestBundle(service, componentBundleLookup);
+ }
+
+ for (final VersionedProcessGroup childGroup : processGroup.getProcessGroups()) {
+ updateToLatestBundles(childGroup, componentBundleLookup);
+ }
+ }
+
+ /**
+ * Updates the given processor to use the latest available bundle version.
+ * See {@link #getLatest(List)} for details on how version comparison is performed.
+ *
+ * @param processor the processor to update
+ * @param componentBundleLookup the lookup used to find available bundles for the processor type
+ */
+ public static void updateToLatestBundle(final VersionedProcessor processor, final ComponentBundleLookup componentBundleLookup) {
+ final Bundle latest = getLatestBundle(processor, componentBundleLookup);
+ processor.setBundle(latest);
+ }
+
+ /**
+ * Updates the given controller service to use the latest available bundle version.
+ * See {@link #getLatest(List)} for details on how version comparison is performed.
+ *
+ * @param service the controller service to update
+ * @param componentBundleLookup the lookup used to find available bundles for the service type
+ */
+ public static void updateToLatestBundle(final VersionedControllerService service, final ComponentBundleLookup componentBundleLookup) {
+ final Bundle latest = getLatestBundle(service, componentBundleLookup);
+ service.setBundle(latest);
+ }
+
+ private static Bundle getLatestBundle(final VersionedConfigurableExtension versionedComponent, final ComponentBundleLookup componentBundleLookup) {
+ final String type = versionedComponent.getType();
+ final List<Bundle> bundles = componentBundleLookup.getAvailableBundles(type);
+ final List<Bundle> includingExisting = new ArrayList<>(bundles);
+ if (versionedComponent.getBundle() != null && !includingExisting.contains(versionedComponent.getBundle())) {
+ includingExisting.add(versionedComponent.getBundle());
+ }
+
+ return getLatest(includingExisting);
+ }
+
+ /**
+ * Returns the bundle with the latest version from the given list.
+ *
+ * <p>Version comparison follows these rules:</p>
+ * <ol>
+ * <li>Versions are split by dots (e.g., "2.1.0" becomes ["2", "1", "0"]).
+ * This also supports calendar-date formats (e.g., "2026.01.01").</li>
+ * <li>Each part is compared numerically when possible; numeric parts are considered
+ * newer than non-numeric parts (e.g., "2.0.0" > "2.0.next")</li>
+ * <li>When base versions are equal, qualifiers are compared with the following precedence
+ * (highest to lowest):
+ * <ul>
+ * <li>Release (no qualifier)</li>
+ * <li>RC (Release Candidate) - e.g., "-RC1", "-RC2"</li>
+ * <li>M (Milestone) - e.g., "-M1", "-M2"</li>
+ * <li>Other/unknown qualifiers</li>
+ * <li>SNAPSHOT</li>
+ * </ul>
+ * </li>
+ * <li>Within the same qualifier type, numeric suffixes are compared
+ * (e.g., "2.0.0-RC2" > "2.0.0-RC1", "2.0.0-M4" > "2.0.0-M1")</li>
+ * </ol>
+ *
+ * <p>Examples of version ordering (highest to lowest):</p>
+ * <ul>
+ * <li>2.0.0 > 2.0.0-RC2 > 2.0.0-RC1 > 2.0.0-M4 > 2.0.0-M1 > 2.0.0-SNAPSHOT</li>
+ * <li>2.1.0-SNAPSHOT > 2.0.0 (higher base version wins)</li>
+ * <li>2026.01.01 > 2025.12.31 (calendar-date format)</li>
+ * </ul>
+ *
+ * @param bundles the list of bundles to compare
+ * @return the bundle with the latest version, or null if the list is empty
+ */
+ public static Bundle getLatest(final List<Bundle> bundles) {
+ Bundle latest = null;
+ for (final Bundle bundle : bundles) {
+ if (latest == null || compareVersion(bundle.getVersion(), latest.getVersion()) > 0) {
+ latest = bundle;
+ }
+ }
+
+ return latest;
+ }
+
+ private static int compareVersion(final String v1, final String v2) {
+ final String baseVersion1 = getBaseVersion(v1);
+ final String baseVersion2 = getBaseVersion(v2);
+
+ final String[] parts1 = baseVersion1.split("\\.");
+ final String[] parts2 = baseVersion2.split("\\.");
+
+ final int length = Math.max(parts1.length, parts2.length);
+ for (int i = 0; i < length; i++) {
+ final String part1Str = i < parts1.length ? parts1[i] : "0";
+ final String part2Str = i < parts2.length ? parts2[i] : "0";
+
+ final int comparison = compareVersionPart(part1Str, part2Str);
+ if (comparison != 0) {
+ return comparison;
+ }
+ }
+
+ // Base versions are equal; compare qualifiers
+ final String qualifier1 = getQualifier(v1);
+ final String qualifier2 = getQualifier(v2);
+ return compareQualifiers(qualifier1, qualifier2);
+ }
+
+ private static int compareQualifiers(final String qualifier1, final String qualifier2) {
+ final int rank1 = getQualifierRank(qualifier1);
+ final int rank2 = getQualifierRank(qualifier2);
+
+ if (rank1 != rank2) {
+ return Integer.compare(rank1, rank2);
+ }
+
+ // Same qualifier type; compare numeric suffixes (e.g., RC2 > RC1, M4 > M3)
+ final int num1 = getQualifierNumber(qualifier1);
+ final int num2 = getQualifierNumber(qualifier2);
+ return Integer.compare(num1, num2);
+ }
+
+ private static int getQualifierRank(final String qualifier) {
+ if (qualifier == null || qualifier.isEmpty()) {
+ return 4;
+ } else if (qualifier.startsWith("RC")) {
+ return 3;
+ } else if (qualifier.startsWith("M")) {
+ return 2;
+ } else if (qualifier.equals("SNAPSHOT")) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ private static int getQualifierNumber(final String qualifier) {
+ if (qualifier == null || qualifier.isEmpty()) {
+ return 0;
+ }
+
+ final StringBuilder digits = new StringBuilder();
+ for (int i = 0; i < qualifier.length(); i++) {
+ final char c = qualifier.charAt(i);
+ if (Character.isDigit(c)) {
+ digits.append(c);
+ }
+ }
+
+ if (digits.isEmpty()) {
+ return 0;
+ }
+
+ try {
+ return Integer.parseInt(digits.toString());
+ } catch (final NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ private static String getQualifier(final String version) {
+ final int qualifierIndex = version.indexOf('-');
+ return qualifierIndex > 0 ? version.substring(qualifierIndex + 1) : null;
+ }
+
+ private static int compareVersionPart(final String part1, final String part2) {
+ final Integer num1 = parseVersionPart(part1);
+ final Integer num2 = parseVersionPart(part2);
+
+ if (num1 != null && num2 != null) {
+ return Integer.compare(num1, num2);
+ } else if (num1 != null) {
+ return 1;
+ } else if (num2 != null) {
+ return -1;
+ } else {
+ return part1.compareTo(part2);
+ }
+ }
+
+ private static Integer parseVersionPart(final String part) {
+ try {
+ return Integer.parseInt(part);
+ } catch (final NumberFormatException e) {
+ return null;
+ }
+ }
+
+ private static String getBaseVersion(final String version) {
+ final int qualifierIndex = version.indexOf('-');
+ return qualifierIndex > 0 ? version.substring(0, qualifierIndex) : version;
+ }
}
diff --git a/nifi-commons/nifi-connector-utils/src/test/java/org/apache/nifi/components/connector/util/TestVersionedFlowUtils.java b/nifi-commons/nifi-connector-utils/src/test/java/org/apache/nifi/components/connector/util/TestVersionedFlowUtils.java
new file mode 100644
index 0000000000..5ff0149d5a
--- /dev/null
+++ b/nifi-commons/nifi-connector-utils/src/test/java/org/apache/nifi/components/connector/util/TestVersionedFlowUtils.java
@@ -0,0 +1,368 @@
+/*
+ * 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.components.connector.util;
+
+import org.apache.nifi.components.connector.ComponentBundleLookup;
+import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.flow.ComponentType;
+import org.apache.nifi.flow.Position;
+import org.apache.nifi.flow.VersionedControllerService;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TestVersionedFlowUtils {
+
+ @Nested
+ class GetLatest {
+ @Test
+ void testMinorVersion() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "1.0.0"),
+ new Bundle("group", "artifact", "1.2.0")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("1.2.0", latestBundle.getVersion());
+ }
+
+ @Test
+ void testMajorVersion() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "1.0.0"),
+ new Bundle("group", "artifact", "2.0.0")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("2.0.0", latestBundle.getVersion());
+ }
+
+ @Test
+ void testMoreDigitsLater() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "1.0"),
+ new Bundle("group", "artifact", "1.0.1")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("1.0.1", latestBundle.getVersion());
+ }
+
+ @Test
+ void testMoreDigitsFirst() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "1.0.1"),
+ new Bundle("group", "artifact", "1.0")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("1.0.1", latestBundle.getVersion());
+ }
+
+ @Test
+ void testWithSnapshotAndSameVersion() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "1.0.0-SNAPSHOT"),
+ new Bundle("group", "artifact", "1.0.0")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("1.0.0", latestBundle.getVersion());
+ }
+
+ @Test
+ void testWithSnapshotAndDifferentVersion() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "1.0.1-SNAPSHOT"),
+ new Bundle("group", "artifact", "1.0.0")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("1.0.1-SNAPSHOT", latestBundle.getVersion());
+ }
+
+ @Test
+ void testEmptyList() {
+ final List<Bundle> bundles = List.of();
+ assertNull(VersionedFlowUtils.getLatest(bundles));
+ }
+
+ @Test
+ void testNonNumericVersionPart() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "4.0.0"),
+ new Bundle("group", "artifact", "4.0.next")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("4.0.0", latestBundle.getVersion());
+ }
+
+ @Test
+ void testFullyNonNumericVersionVsNumeric() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "1.0.0"),
+ new Bundle("group", "artifact", "undefined")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("1.0.0", latestBundle.getVersion());
+ }
+
+ @Test
+ void testTwoFullyNonNumericVersions() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "undefined"),
+ new Bundle("group", "artifact", "unknown")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("unknown", latestBundle.getVersion());
+ }
+
+ @Test
+ void testQualifierOrdering() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "2.0.0-SNAPSHOT"),
+ new Bundle("group", "artifact", "2.0.0-M1"),
+ new Bundle("group", "artifact", "2.0.0-M4"),
+ new Bundle("group", "artifact", "2.0.0-RC1"),
+ new Bundle("group", "artifact", "2.0.0-RC2"),
+ new Bundle("group", "artifact", "2.0.0")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("2.0.0", latestBundle.getVersion());
+ }
+
+ @Test
+ void testQualifierOrderingWithoutRelease() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "2.0.0-SNAPSHOT"),
+ new Bundle("group", "artifact", "2.0.0-M1"),
+ new Bundle("group", "artifact", "2.0.0-M4"),
+ new Bundle("group", "artifact", "2.0.0-RC1"),
+ new Bundle("group", "artifact", "2.0.0-RC2")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("2.0.0-RC2", latestBundle.getVersion());
+ }
+
+ @Test
+ void testMilestoneVersionsOrdering() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "2.0.0-M1"),
+ new Bundle("group", "artifact", "2.0.0-M4"),
+ new Bundle("group", "artifact", "2.0.0-SNAPSHOT")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("2.0.0-M4", latestBundle.getVersion());
+ }
+
+ @Test
+ void testCalendarDateFormat() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "2025.12.31"),
+ new Bundle("group", "artifact", "2026.01.01")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("2026.01.01", latestBundle.getVersion());
+ }
+
+ @Test
+ void testCalendarDateFormatWithBuildNumber() {
+ final List<Bundle> bundles = List.of(
+ new Bundle("group", "artifact", "2025.12.31.999"),
+ new Bundle("group", "artifact", "2026.01.01.451")
+ );
+
+ final Bundle latestBundle = VersionedFlowUtils.getLatest(bundles);
+ assertNotNull(latestBundle);
+ assertEquals("2026.01.01.451", latestBundle.getVersion());
+ }
+ }
+
+ @Nested
+ class UpdateToLatestBundles {
+ private static final String PROCESSOR_TYPE = "org.apache.nifi.processors.TestProcessor";
+ private static final String SERVICE_TYPE = "org.apache.nifi.services.TestService";
+
+ @Test
+ void testLookupReturnsOneOlderBundle() {
+ final VersionedProcessGroup group = createProcessGroup();
+ final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0));
+
+ final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class);
+ when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of(new Bundle("group", "artifact", "1.0.0")));
+
+ VersionedFlowUtils.updateToLatestBundles(group, lookup);
+
+ assertEquals("2.0.0", processor.getBundle().getVersion());
+ }
+
+ @Test
+ void testLookupReturnsOneNewerBundle() {
+ final VersionedProcessGroup group = createProcessGroup();
+ final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0));
+
+ final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class);
+ when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of(new Bundle("group", "artifact", "3.0.0")));
+
+ VersionedFlowUtils.updateToLatestBundles(group, lookup);
+
+ assertEquals("3.0.0", processor.getBundle().getVersion());
+ }
+
+ @Test
+ void testLookupReturnsMultipleBundlesIncludingSameOlderAndNewer() {
+ final VersionedProcessGroup group = createProcessGroup();
+ final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0));
+
+ final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class);
+ when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of(
+ new Bundle("group", "artifact", "1.0.0"),
+ new Bundle("group", "artifact", "2.0.0"),
+ new Bundle("group", "artifact", "3.0.0"),
+ new Bundle("group", "artifact", "4.0.0")
+ ));
+
+ VersionedFlowUtils.updateToLatestBundles(group, lookup);
+
+ assertEquals("4.0.0", processor.getBundle().getVersion());
+ }
+
+ @Test
+ void testLookupReturnsNoBundles() {
+ final VersionedProcessGroup group = createProcessGroup();
+ final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0));
+
+ final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class);
+ when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of());
+
+ VersionedFlowUtils.updateToLatestBundles(group, lookup);
+
+ assertEquals("2.0.0", processor.getBundle().getVersion());
+ }
+
+ @Test
+ void testControllerServiceUpdatedToNewerBundle() {
+ final VersionedProcessGroup group = createProcessGroup();
+ final VersionedControllerService service = VersionedFlowUtils.addControllerService(group, SERVICE_TYPE, new Bundle("group", "artifact", "1.5.0"), "Test Service");
+
+ final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class);
+ when(lookup.getAvailableBundles(SERVICE_TYPE)).thenReturn(List.of(new Bundle("group", "artifact", "2.5.0")));
+
+ VersionedFlowUtils.updateToLatestBundles(group, lookup);
+
+ assertEquals("2.5.0", service.getBundle().getVersion());
+ }
+
+ @Test
+ void testNestedProcessorUpdatedToNewerBundle() {
+ final VersionedProcessGroup rootGroup = createProcessGroup();
+ final VersionedProcessGroup childGroup = createChildProcessGroup(rootGroup, "child-group-id");
+ final VersionedProcessor nestedProcessor = VersionedFlowUtils.addProcessor(childGroup, PROCESSOR_TYPE, new Bundle("group", "artifact", "1.0.0"), "Nested Processor", new Position(0, 0));
+
+ final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class);
+ when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of(new Bundle("group", "artifact", "5.0.0")));
+
+ VersionedFlowUtils.updateToLatestBundles(rootGroup, lookup);
+
+ assertEquals("5.0.0", nestedProcessor.getBundle().getVersion());
+ }
+
+ @Test
+ void testLookupReturnsVersionsWithQualifiers() {
+ final VersionedProcessGroup group = createProcessGroup();
+ final VersionedProcessor processor = VersionedFlowUtils.addProcessor(group, PROCESSOR_TYPE, new Bundle("group", "artifact", "2.0.0"), "Test Processor", new Position(0, 0));
+
+ final ComponentBundleLookup lookup = mock(ComponentBundleLookup.class);
+ when(lookup.getAvailableBundles(PROCESSOR_TYPE)).thenReturn(List.of(
+ new Bundle("group", "artifact", "2.0.0"),
+ new Bundle("group", "artifact", "2.0.0-SNAPSHOT"),
+ new Bundle("group", "artifact", "2.0.0-M1"),
+ new Bundle("group", "artifact", "2.0.0-RC1")
+ ));
+
+ VersionedFlowUtils.updateToLatestBundles(group, lookup);
+
+ assertEquals("2.0.0", processor.getBundle().getVersion());
+ }
+
+ private VersionedProcessGroup createProcessGroup() {
+ final VersionedProcessGroup group = new VersionedProcessGroup();
+ group.setIdentifier("test-group-id");
+ group.setName("Test Process Group");
+ group.setProcessors(new HashSet<>());
+ group.setProcessGroups(new HashSet<>());
+ group.setControllerServices(new HashSet<>());
+ group.setConnections(new HashSet<>());
+ group.setInputPorts(new HashSet<>());
+ group.setOutputPorts(new HashSet<>());
+ group.setFunnels(new HashSet<>());
+ group.setLabels(new HashSet<>());
+ group.setComponentType(ComponentType.PROCESS_GROUP);
+ return group;
+ }
+
+ private VersionedProcessGroup createChildProcessGroup(final VersionedProcessGroup parent, final String identifier) {
+ final VersionedProcessGroup childGroup = new VersionedProcessGroup();
+ childGroup.setIdentifier(identifier);
+ childGroup.setName("Child Process Group");
+ childGroup.setGroupIdentifier(parent.getIdentifier());
+ childGroup.setProcessors(new HashSet<>());
+ childGroup.setProcessGroups(new HashSet<>());
+ childGroup.setControllerServices(new HashSet<>());
+ childGroup.setConnections(new HashSet<>());
+ childGroup.setInputPorts(new HashSet<>());
+ childGroup.setOutputPorts(new HashSet<>());
+ childGroup.setFunnels(new HashSet<>());
+ childGroup.setLabels(new HashSet<>());
+ childGroup.setComponentType(ComponentType.PROCESS_GROUP);
+ parent.getProcessGroups().add(childGroup);
+ return childGroup;
+ }
+ }
+
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
index 93cb6fd44a..d4af4dcbb2 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
@@ -18,12 +18,13 @@
package org.apache.nifi.mock.connector.server;
import org.apache.nifi.asset.AssetManager;
-import org.apache.nifi.components.connector.FrameworkConnectorInitializationContextBuilder;
+import org.apache.nifi.components.connector.ComponentBundleLookup;
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.components.connector.FrameworkConnectorInitializationContext;
+import org.apache.nifi.components.connector.FrameworkConnectorInitializationContextBuilder;
import org.apache.nifi.components.connector.FrameworkFlowContext;
-import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.flow.VersionedProcessor;
@@ -36,6 +37,7 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni
private final ComponentLog componentLog;
private final SecretsManager secretsManager;
private final AssetManager assetManager;
+ private final ComponentBundleLookup componentBundleLookup;
private final MockExtensionMapper mockExtensionMapper;
@@ -46,6 +48,7 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni
this.secretsManager = builder.secretsManager;
this.assetManager = builder.assetManager;
this.mockExtensionMapper = builder.mockExtensionMapper;
+ this.componentBundleLookup = builder.componentBundleLookup;
}
@@ -64,6 +67,11 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni
return componentLog;
}
+ @Override
+ public ComponentBundleLookup getComponentBundleLookup() {
+ return componentBundleLookup;
+ }
+
@Override
public SecretsManager getSecretsManager() {
return secretsManager;
@@ -118,6 +126,7 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni
private ComponentLog componentLog;
private SecretsManager secretsManager;
private AssetManager assetManager;
+ private ComponentBundleLookup componentBundleLookup;
public Builder(final MockExtensionMapper mockExtensionMapper) {
this.mockExtensionMapper = mockExtensionMapper;
@@ -147,6 +156,12 @@ public class MockConnectorInitializationContext implements FrameworkConnectorIni
return this;
}
+ @Override
+ public Builder componentBundleLookup(final ComponentBundleLookup bundleLookup) {
+ this.componentBundleLookup = bundleLookup;
+ return this;
+ }
+
@Override
public Builder assetManager(final AssetManager assetManager) {
this.assetManager = assetManager;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java
index 030e333a84..9ef92941a8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/FrameworkConnectorInitializationContextBuilder.java
@@ -33,5 +33,7 @@ public interface FrameworkConnectorInitializationContextBuilder {
FrameworkConnectorInitializationContextBuilder secretsManager(SecretsManager secretsManager);
+ FrameworkConnectorInitializationContextBuilder componentBundleLookup(ComponentBundleLookup bundleLookup);
+
FrameworkConnectorInitializationContext build();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardComponentBundleLookup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardComponentBundleLookup.java
new file mode 100644
index 0000000000..84d664d80b
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardComponentBundleLookup.java
@@ -0,0 +1,45 @@
+/*
+ * 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.components.connector;
+
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.nar.ExtensionManager;
+
+import java.util.List;
+
+public class StandardComponentBundleLookup implements ComponentBundleLookup {
+ private final ExtensionManager extensionManager;
+
+ public StandardComponentBundleLookup(final ExtensionManager extensionManager) {
+ this.extensionManager = extensionManager;
+ }
+
+ @Override
+ public List<Bundle> getAvailableBundles(final String componentType) {
+ final List<org.apache.nifi.bundle.Bundle> bundles = extensionManager.getBundles(componentType);
+ return bundles.stream()
+ .map(this::convertBundle)
+ .toList();
+ }
+
+ private Bundle convertBundle(final org.apache.nifi.bundle.Bundle bundle) {
+ final BundleCoordinate coordinate = bundle.getBundleDetails().getCoordinate();
+ return new Bundle(coordinate.getGroup(), coordinate.getId(), coordinate.getVersion());
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
index c31cad237f..ab2dcbf399 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorInitializationContext.java
@@ -30,6 +30,7 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto
private final ComponentLog componentLog;
private final SecretsManager secretsManager;
private final AssetManager assetManager;
+ private final ComponentBundleLookup componentBundleLookup;
private StandardConnectorInitializationContext(final Builder builder) {
@@ -38,9 +39,9 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto
this.componentLog = builder.componentLog;
this.secretsManager = builder.secretsManager;
this.assetManager = builder.assetManager;
+ this.componentBundleLookup = builder.componentBundleLookup;
}
-
@Override
public String getIdentifier() {
return identifier;
@@ -56,6 +57,11 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto
return componentLog;
}
+ @Override
+ public ComponentBundleLookup getComponentBundleLookup() {
+ return componentBundleLookup;
+ }
+
@Override
public SecretsManager getSecretsManager() {
return secretsManager;
@@ -90,6 +96,7 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto
private ComponentLog componentLog;
private SecretsManager secretsManager;
private AssetManager assetManager;
+ private ComponentBundleLookup componentBundleLookup;
@Override
public Builder identifier(final String identifier) {
@@ -121,6 +128,12 @@ public class StandardConnectorInitializationContext implements FrameworkConnecto
return this;
}
+ @Override
+ public Builder componentBundleLookup(final ComponentBundleLookup bundleLookup) {
+ this.componentBundleLookup = bundleLookup;
+ return this;
+ }
+
@Override
public StandardConnectorInitializationContext build() {
return new StandardConnectorInitializationContext(this);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index 6d985e297d..64f06ded7c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -28,6 +28,7 @@ import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.connector.ComponentBundleLookup;
import org.apache.nifi.components.connector.ConfigurationStep;
import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorDetails;
@@ -147,6 +148,7 @@ public class ExtensionBuilder {
private ConnectorStateTransition connectorStateTransition;
private FrameworkConnectorInitializationContextBuilder connectorInitializationContextBuilder;
private ConnectorValidationTrigger connectorValidationTrigger;
+ private ComponentBundleLookup componentBundleLookup;
public ExtensionBuilder type(final String type) {
this.type = type;
@@ -281,6 +283,12 @@ public class ExtensionBuilder {
return this;
}
+ public ExtensionBuilder componentBundleLookup(final ComponentBundleLookup componentBundleLookup) {
+ this.componentBundleLookup = componentBundleLookup;
+ return this;
+ }
+
+
public ProcessorNode buildProcessor() {
requireNonNull(identifier, "Processor ID");
requireNonNull(type, "Processor Type");
@@ -574,7 +582,7 @@ public class ExtensionBuilder {
connectorNode.initializeConnector(initContext);
return connectorNode;
- }
+ }
private void initializeDefaultValues(final Connector connector, final FrameworkFlowContext flowContext) {
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connector.getClass(), identifier)) {
@@ -605,6 +613,7 @@ public class ExtensionBuilder {
.componentLog(componentLog)
.secretsManager(flowController.getConnectorRepository().getSecretsManager())
.assetManager(flowController.getConnectorAssetManager())
+ .componentBundleLookup(componentBundleLookup)
.build();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index d3874eb5cb..3826c9a371 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -27,12 +27,14 @@ import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.connector.ComponentBundleLookup;
import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorStateTransition;
import org.apache.nifi.components.connector.FlowContextFactory;
import org.apache.nifi.components.connector.ProcessGroupFactory;
+import org.apache.nifi.components.connector.StandardComponentBundleLookup;
import org.apache.nifi.components.connector.StandardConnectorConfigurationContext;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.ConnectableType;
@@ -763,6 +765,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
final ProcessGroupFactory processGroupFactory = groupId -> createProcessGroup(groupId, id);
final FlowContextFactory flowContextFactory = new FlowControllerFlowContextFactory(flowController, managedRootGroup, activeConfigurationContext, processGroupFactory);
+ final ComponentBundleLookup componentBundleLookup = new StandardComponentBundleLookup(extensionManager);
final ConnectorNode connectorNode = new ExtensionBuilder()
.identifier(id)
@@ -776,6 +779,7 @@ public class StandardFlowManager extends AbstractFlowManager implements FlowMana
.connectorStateTransition(stateTransition)
.connectorInitializationContextBuilder(flowController.getConnectorRepository().createInitializationContextBuilder())
.connectorValidationTrigger(flowController.getConnectorValidationTrigger())
+ .componentBundleLookup(componentBundleLookup)
.buildConnector(firstTimeAdded);
// Establish the Connector as the parent authorizable of the managed root group
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java
index db43577038..d8763e0587 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/CalculateConnector.java
@@ -100,9 +100,6 @@ public class CalculateConnector extends AbstractConnector {
private Calculation calculation;
private int result;
- public CalculatedResult() {
- }
-
public Calculation getCalculation() {
return calculation;
}
(nifi) 34/40: NIFI-15472: Added addAsset methods to ConnectorTestRunner (#10776)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit b0481062fabe3e2d8a744d7225615a896ae578e4
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Jan 16 18:29:54 2026 -0500
NIFI-15472: Added addAsset methods to ConnectorTestRunner (#10776)
---
.../mock/connector/server/ConnectorTestRunner.java | 7 +
.../server/MockConnectorAssetManager.java | 179 +++++++++++++++
.../server/StandardConnectorMockServer.java | 29 +++
.../server/MockConnectorAssetManagerTest.java | 245 +++++++++++++++++++++
.../connector/StandardConnectorTestRunner.java | 11 +
.../src/main/resources/nifi.properties | 3 +
6 files changed, 474 insertions(+)
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
index 5fe172fa78..25a5215db3 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
@@ -18,12 +18,15 @@
package org.apache.nifi.mock.connector.server;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.components.connector.SecretReference;
import org.apache.nifi.components.connector.StepConfiguration;
import java.io.Closeable;
+import java.io.File;
+import java.io.InputStream;
import java.time.Duration;
import java.util.List;
import java.util.Map;
@@ -50,6 +53,10 @@ public interface ConnectorTestRunner extends Closeable {
void addSecret(String name, String value);
+ AssetReference addAsset(File file);
+
+ AssetReference addAsset(String assetName, InputStream contents);
+
void startConnector();
void stopConnector();
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorAssetManager.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorAssetManager.java
new file mode 100644
index 0000000000..e8272be173
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorAssetManager.java
@@ -0,0 +1,179 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.asset.Asset;
+import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.asset.AssetManagerInitializationContext;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A Mock implementation of AssetManager for use in ConnectorTestRunner tests.
+ * This implementation stores assets in a temporary directory structure.
+ */
+public class MockConnectorAssetManager implements AssetManager {
+
+ private static final String ASSET_STORAGE_LOCATION_PROPERTY = "directory";
+ private static final String DEFAULT_ASSET_STORAGE_LOCATION = "target/mock-connector-assets";
+
+ private final Map<String, Asset> assets = new ConcurrentHashMap<>();
+ private volatile File assetStorageLocation;
+
+ @Override
+ public void initialize(final AssetManagerInitializationContext context) {
+ final String storageLocation = context.getProperties().getOrDefault(ASSET_STORAGE_LOCATION_PROPERTY, DEFAULT_ASSET_STORAGE_LOCATION);
+ assetStorageLocation = new File(storageLocation);
+
+ if (!assetStorageLocation.exists()) {
+ try {
+ Files.createDirectories(assetStorageLocation.toPath());
+ } catch (final IOException e) {
+ throw new RuntimeException("Failed to create asset storage directory: " + storageLocation, e);
+ }
+ }
+ }
+
+ @Override
+ public Asset createAsset(final String ownerId, final String assetName, final InputStream contents) throws IOException {
+ final String assetId = UUID.randomUUID().toString();
+ return saveAsset(ownerId, assetId, assetName, contents);
+ }
+
+ @Override
+ public Asset saveAsset(final String ownerId, final String assetId, final String assetName, final InputStream contents) throws IOException {
+ final File assetFile = getFile(ownerId, assetId, assetName);
+ final File parentDir = assetFile.getParentFile();
+
+ if (!parentDir.exists()) {
+ Files.createDirectories(parentDir.toPath());
+ }
+
+ Files.copy(contents, assetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ final Asset asset = new MockAsset(assetId, ownerId, assetName, assetFile, null);
+ assets.put(assetId, asset);
+ return asset;
+ }
+
+ @Override
+ public Optional<Asset> getAsset(final String id) {
+ return Optional.ofNullable(assets.get(id));
+ }
+
+ @Override
+ public List<Asset> getAssets(final String ownerId) {
+ final List<Asset> ownerAssets = new ArrayList<>();
+ for (final Asset asset : assets.values()) {
+ if (asset.getOwnerIdentifier().equals(ownerId)) {
+ ownerAssets.add(asset);
+ }
+ }
+ return ownerAssets;
+ }
+
+ @Override
+ public Asset createMissingAsset(final String ownerId, final String assetName) {
+ final String assetId = UUID.randomUUID().toString();
+ final File file = getFile(ownerId, assetId, assetName);
+ final Asset asset = new MockAsset(assetId, ownerId, assetName, file, null);
+ assets.put(assetId, asset);
+ return asset;
+ }
+
+ @Override
+ public Optional<Asset> deleteAsset(final String id) {
+ final Asset removed = assets.remove(id);
+ if (removed != null && removed.getFile().exists()) {
+ try {
+ Files.delete(removed.getFile().toPath());
+ } catch (final IOException e) {
+ throw new RuntimeException("Failed to delete asset " + id + " from storage file " + removed.getFile().getAbsolutePath(), e);
+ }
+ }
+
+ return Optional.ofNullable(removed);
+ }
+
+ private File getFile(final String ownerId, final String assetId, final String assetName) {
+ final Path parentPath = assetStorageLocation.toPath().normalize();
+ final Path assetPath = Path.of(ownerId, assetId, assetName).normalize();
+ return parentPath.resolve(assetPath).toFile();
+ }
+
+ /**
+ * A simple Asset implementation for the mock.
+ */
+ private static class MockAsset implements Asset {
+ private final String identifier;
+ private final String ownerIdentifier;
+ private final String name;
+ private final File file;
+ private final String digest;
+
+ MockAsset(final String identifier, final String ownerIdentifier, final String name, final File file, final String digest) {
+ this.identifier = identifier;
+ this.ownerIdentifier = ownerIdentifier;
+ this.name = name;
+ this.file = file;
+ this.digest = digest;
+ }
+
+ @Override
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ @Deprecated
+ public String getParameterContextIdentifier() {
+ return ownerIdentifier;
+ }
+
+ @Override
+ public String getOwnerIdentifier() {
+ return ownerIdentifier;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public File getFile() {
+ return file;
+ }
+
+ @Override
+ public Optional<String> getDigest() {
+ return Optional.ofNullable(digest);
+ }
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
index 38a61ae9bd..685e84748a 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
@@ -18,6 +18,8 @@
package org.apache.nifi.mock.connector.server;
import org.apache.nifi.admin.service.AuditService;
+import org.apache.nifi.asset.Asset;
+import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
@@ -25,6 +27,7 @@ import org.apache.nifi.bundle.BundleDetails;
import org.apache.nifi.cluster.ClusterDetailsFactory;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorState;
@@ -58,7 +61,9 @@ import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.validation.RuleViolationsManager;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
@@ -265,6 +270,30 @@ public class StandardConnectorMockServer implements ConnectorMockServer {
return new SecretReference(ConnectorTestRunner.SECRET_PROVIDER_ID, ConnectorTestRunner.SECRET_PROVIDER_NAME, secretName, secretName);
}
+ @Override
+ public AssetReference addAsset(final File file) {
+ final AssetManager assetManager = flowController.getConnectorAssetManager();
+
+ try (final InputStream inputStream = new FileInputStream(file)) {
+ final Asset asset = assetManager.createAsset(CONNECTOR_ID, file.getName(), inputStream);
+ return new AssetReference(Set.of(asset.getIdentifier()));
+ } catch (final IOException e) {
+ throw new RuntimeException("Failed to add asset from file: " + file.getAbsolutePath(), e);
+ }
+ }
+
+ @Override
+ public AssetReference addAsset(final String assetName, final InputStream contents) {
+ final AssetManager assetManager = flowController.getConnectorAssetManager();
+
+ try {
+ final Asset asset = assetManager.createAsset(CONNECTOR_ID, assetName, contents);
+ return new AssetReference(Set.of(asset.getIdentifier()));
+ } catch (final IOException e) {
+ throw new RuntimeException("Failed to add asset: " + assetName, e);
+ }
+ }
+
@Override
public void startConnector() {
initialFlowFileTransferCounts = connectorNode.getFlowFileTransferCounts();
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockConnectorAssetManagerTest.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockConnectorAssetManagerTest.java
new file mode 100644
index 0000000000..2952178764
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockConnectorAssetManagerTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.asset.Asset;
+import org.apache.nifi.asset.AssetManagerInitializationContext;
+import org.apache.nifi.asset.AssetReferenceLookup;
+import org.apache.nifi.controller.NodeTypeProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class MockConnectorAssetManagerTest {
+
+ private static final String CONNECTOR_ID = "test-connector";
+ private static final String ASSET_NAME = "test-asset.txt";
+ private static final String ASSET_CONTENTS = "test-contents";
+
+ @TempDir
+ private Path tempDir;
+
+ private MockConnectorAssetManager assetManager;
+
+ @BeforeEach
+ void setUp() {
+ assetManager = new MockConnectorAssetManager();
+ final AssetManagerInitializationContext context = createInitializationContext(tempDir);
+ assetManager.initialize(context);
+ }
+
+ @Test
+ void testCreateAsset() throws IOException {
+ final InputStream contents = new ByteArrayInputStream(ASSET_CONTENTS.getBytes(StandardCharsets.UTF_8));
+
+ final Asset asset = assetManager.createAsset(CONNECTOR_ID, ASSET_NAME, contents);
+
+ assertNotNull(asset);
+ assertNotNull(asset.getIdentifier());
+ assertEquals(CONNECTOR_ID, asset.getOwnerIdentifier());
+ assertEquals(ASSET_NAME, asset.getName());
+ assertTrue(asset.getFile().exists());
+ assertEquals(ASSET_CONTENTS, Files.readString(asset.getFile().toPath(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void testCreateAssetGeneratesUniqueIdentifiers() throws IOException {
+ final InputStream contents1 = new ByteArrayInputStream(ASSET_CONTENTS.getBytes(StandardCharsets.UTF_8));
+ final InputStream contents2 = new ByteArrayInputStream(ASSET_CONTENTS.getBytes(StandardCharsets.UTF_8));
+
+ final Asset asset1 = assetManager.createAsset(CONNECTOR_ID, ASSET_NAME, contents1);
+ final Asset asset2 = assetManager.createAsset(CONNECTOR_ID, ASSET_NAME, contents2);
+
+ assertNotEquals(asset1.getIdentifier(), asset2.getIdentifier());
+ }
+
+ @Test
+ void testSaveAsset() throws IOException {
+ final String assetId = "specific-asset-id";
+ final InputStream contents = new ByteArrayInputStream(ASSET_CONTENTS.getBytes(StandardCharsets.UTF_8));
+
+ final Asset asset = assetManager.saveAsset(CONNECTOR_ID, assetId, ASSET_NAME, contents);
+
+ assertNotNull(asset);
+ assertEquals(assetId, asset.getIdentifier());
+ assertEquals(CONNECTOR_ID, asset.getOwnerIdentifier());
+ assertEquals(ASSET_NAME, asset.getName());
+ assertTrue(asset.getFile().exists());
+ assertEquals(ASSET_CONTENTS, Files.readString(asset.getFile().toPath(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void testSaveAssetOverwritesExisting() throws IOException {
+ final String assetId = "overwrite-asset-id";
+ final String originalContents = "original";
+ final String updatedContents = "updated";
+
+ final InputStream originalStream = new ByteArrayInputStream(originalContents.getBytes(StandardCharsets.UTF_8));
+ final Asset originalAsset = assetManager.saveAsset(CONNECTOR_ID, assetId, ASSET_NAME, originalStream);
+ assertEquals(originalContents, Files.readString(originalAsset.getFile().toPath(), StandardCharsets.UTF_8));
+
+ final InputStream updatedStream = new ByteArrayInputStream(updatedContents.getBytes(StandardCharsets.UTF_8));
+ final Asset updatedAsset = assetManager.saveAsset(CONNECTOR_ID, assetId, ASSET_NAME, updatedStream);
+ assertEquals(updatedContents, Files.readString(updatedAsset.getFile().toPath(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ void testGetAsset() throws IOException {
+ final InputStream contents = new ByteArrayInputStream(ASSET_CONTENTS.getBytes(StandardCharsets.UTF_8));
+ final Asset createdAsset = assetManager.createAsset(CONNECTOR_ID, ASSET_NAME, contents);
+
+ final Optional<Asset> retrieved = assetManager.getAsset(createdAsset.getIdentifier());
+
+ assertTrue(retrieved.isPresent());
+ assertEquals(createdAsset.getIdentifier(), retrieved.get().getIdentifier());
+ assertEquals(createdAsset.getName(), retrieved.get().getName());
+ assertEquals(createdAsset.getOwnerIdentifier(), retrieved.get().getOwnerIdentifier());
+ }
+
+ @Test
+ void testGetAssetReturnsEmptyForNonExistent() {
+ final Optional<Asset> retrieved = assetManager.getAsset("non-existent-id");
+
+ assertTrue(retrieved.isEmpty());
+ }
+
+ @Test
+ void testGetAssets() throws IOException {
+ final InputStream contents1 = new ByteArrayInputStream("content1".getBytes(StandardCharsets.UTF_8));
+ final InputStream contents2 = new ByteArrayInputStream("content2".getBytes(StandardCharsets.UTF_8));
+ final InputStream contents3 = new ByteArrayInputStream("content3".getBytes(StandardCharsets.UTF_8));
+
+ assetManager.createAsset(CONNECTOR_ID, "asset1.txt", contents1);
+ assetManager.createAsset(CONNECTOR_ID, "asset2.txt", contents2);
+ assetManager.createAsset("other-connector", "asset3.txt", contents3);
+
+ final List<Asset> connectorAssets = assetManager.getAssets(CONNECTOR_ID);
+ assertEquals(2, connectorAssets.size());
+ assertTrue(connectorAssets.stream().allMatch(a -> a.getOwnerIdentifier().equals(CONNECTOR_ID)));
+
+ final List<Asset> otherAssets = assetManager.getAssets("other-connector");
+ assertEquals(1, otherAssets.size());
+ assertEquals("other-connector", otherAssets.get(0).getOwnerIdentifier());
+ }
+
+ @Test
+ void testGetAssetsReturnsEmptyListForUnknownOwner() {
+ final List<Asset> assets = assetManager.getAssets("unknown-owner");
+
+ assertNotNull(assets);
+ assertTrue(assets.isEmpty());
+ }
+
+ @Test
+ void testCreateMissingAsset() {
+ final Asset missingAsset = assetManager.createMissingAsset(CONNECTOR_ID, ASSET_NAME);
+
+ assertNotNull(missingAsset);
+ assertNotNull(missingAsset.getIdentifier());
+ assertEquals(CONNECTOR_ID, missingAsset.getOwnerIdentifier());
+ assertEquals(ASSET_NAME, missingAsset.getName());
+ assertFalse(missingAsset.getFile().exists());
+ assertTrue(missingAsset.getDigest().isEmpty());
+ }
+
+ @Test
+ void testCreateMissingAssetCanBeRetrieved() {
+ final Asset missingAsset = assetManager.createMissingAsset(CONNECTOR_ID, ASSET_NAME);
+
+ final Optional<Asset> retrieved = assetManager.getAsset(missingAsset.getIdentifier());
+ assertTrue(retrieved.isPresent());
+ assertEquals(missingAsset.getIdentifier(), retrieved.get().getIdentifier());
+
+ final List<Asset> ownerAssets = assetManager.getAssets(CONNECTOR_ID);
+ assertEquals(1, ownerAssets.size());
+ }
+
+ @Test
+ void testDeleteAsset() throws IOException {
+ final InputStream contents = new ByteArrayInputStream(ASSET_CONTENTS.getBytes(StandardCharsets.UTF_8));
+ final Asset createdAsset = assetManager.createAsset(CONNECTOR_ID, ASSET_NAME, contents);
+ final Path assetFilePath = createdAsset.getFile().toPath();
+ assertTrue(Files.exists(assetFilePath));
+
+ final Optional<Asset> deleted = assetManager.deleteAsset(createdAsset.getIdentifier());
+
+ assertTrue(deleted.isPresent());
+ assertEquals(createdAsset.getIdentifier(), deleted.get().getIdentifier());
+ assertFalse(Files.exists(assetFilePath));
+ assertTrue(assetManager.getAsset(createdAsset.getIdentifier()).isEmpty());
+ }
+
+ @Test
+ void testDeleteAssetReturnsEmptyForNonExistent() {
+ final Optional<Asset> deleted = assetManager.deleteAsset("non-existent-id");
+
+ assertTrue(deleted.isEmpty());
+ }
+
+ @Test
+ void testInitializeCreatesStorageDirectory() {
+ final Path newStorageDir = tempDir.resolve("new-storage");
+ assertFalse(Files.exists(newStorageDir));
+
+ final MockConnectorAssetManager newManager = new MockConnectorAssetManager();
+ final AssetManagerInitializationContext context = createInitializationContext(newStorageDir);
+ newManager.initialize(context);
+
+ assertTrue(Files.exists(newStorageDir));
+ assertTrue(Files.isDirectory(newStorageDir));
+ }
+
+ private AssetManagerInitializationContext createInitializationContext(final Path storageDirectory) {
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("directory", storageDirectory.toAbsolutePath().toString());
+
+ return new AssetManagerInitializationContext() {
+ @Override
+ public AssetReferenceLookup getAssetReferenceLookup() {
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+
+ @Override
+ public NodeTypeProvider getNodeTypeProvider() {
+ return null;
+ }
+ };
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
index 71636a5af1..d2b7e79f0b 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
@@ -20,6 +20,7 @@ package org.apache.nifi.mock.connector;
import org.apache.nifi.NiFiServer;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.components.connector.SecretReference;
@@ -165,6 +166,16 @@ public class StandardConnectorTestRunner implements ConnectorTestRunner, Closeab
mockServer.addSecret(name, value);
}
+ @Override
+ public AssetReference addAsset(final File file) {
+ return mockServer.addAsset(file);
+ }
+
+ @Override
+ public AssetReference addAsset(final String assetName, final InputStream contents) {
+ return mockServer.addAsset(assetName, contents);
+ }
+
@Override
public void startConnector() {
mockServer.startConnector();
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties
index 071f1cca3c..44deabc71a 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties
@@ -94,6 +94,9 @@ nifi.nar.persistence.provider.properties.directory=target/nifi-storage/nar_repos
# Asset Management
nifi.asset.manager.properties.directory=target/nifi-storage/assets
+# Connector Asset Manager
+nifi.connector.asset.manager.implementation=org.apache.nifi.mock.connector.server.MockConnectorAssetManager
+
# Secrets Manager
nifi.secrets.manager.implementation=org.apache.nifi.mock.connector.server.secrets.ConnectorTestRunnerSecretsManager
(nifi) 03/40: NIFI-15258: Fixed checkstyle violations/pmd
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit f80a0b73872d07cb4543693e7091e0bf75c5fa6b
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Tue Dec 9 09:13:07 2025 -0500
NIFI-15258: Fixed checkstyle violations/pmd
---
.../apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java | 1 -
.../org/apache/nifi/mock/connector/server/ConnectorTestRunner.java | 1 -
.../apache/nifi/mock/connector/server/MockRuleViolationsManager.java | 4 ++--
.../main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java | 1 -
.../java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java | 3 +--
.../test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java | 1 -
.../java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java | 4 ++--
.../nifi/controller/service/StandardControllerServiceNode.java | 1 -
.../main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java | 1 -
.../components/connector/MutableConnectorConfigurationContext.java | 1 -
.../java/org/apache/nifi/components/connector/GhostConnector.java | 4 ++--
.../java/org/apache/nifi/components/connector/BlockingConnector.java | 4 ++--
.../java/org/apache/nifi/components/connector/SleepingConnector.java | 5 ++---
.../apache/nifi/components/connector/TestStandardConnectorNode.java | 4 +++-
.../src/test/java/org/apache/nifi/controller/flow/NopConnector.java | 4 ++--
.../src/test/java/org/apache/nifi/nar/DummyConnector.java | 4 ++--
.../org/apache/nifi/authorization/StandardAuthorizableLookup.java | 2 --
.../src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java | 1 -
18 files changed, 18 insertions(+), 28 deletions(-)
diff --git a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/test/java/org/apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/test/java/org/apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java
index 6ba9ea9221..18252878cf 100644
--- a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/test/java/org/apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java
+++ b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/test/java/org/apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java
@@ -31,7 +31,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
import static org.apache.nifi.c2.protocol.api.C2OperationState.OperationState.FULLY_APPLIED;
import static org.apache.nifi.c2.protocol.api.C2OperationState.OperationState.NOT_APPLIED;
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
index 2c3eaa40ee..798f6d4676 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
@@ -20,7 +20,6 @@ package org.apache.nifi.mock.connector.server;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.FlowUpdateException;
import org.apache.nifi.components.connector.PropertyGroupConfiguration;
-import org.apache.nifi.processor.Processor;
import java.io.Closeable;
import java.time.Duration;
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockRuleViolationsManager.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockRuleViolationsManager.java
index 4c3d5fd8c5..bb0e2636ea 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockRuleViolationsManager.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockRuleViolationsManager.java
@@ -33,8 +33,8 @@ public class MockRuleViolationsManager implements RuleViolationsManager {
}
@Override
- public void upsertGroupViolations(final VersionedProcessGroup processGroup, final Collection<RuleViolation> violations, final Map<VersionedComponent, Collection<RuleViolation>> componentToRuleViolations) {
-
+ public void upsertGroupViolations(final VersionedProcessGroup processGroup, final Collection<RuleViolation> violations,
+ final Map<VersionedComponent, Collection<RuleViolation>> componentToRuleViolations) {
}
@Override
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
index f9775bc2d8..617a1ca055 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
@@ -27,7 +27,6 @@ import org.apache.nifi.components.connector.util.VersionedFlowUtils;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
-import java.util.Map;
public class GenerateAndLog extends AbstractConnector {
diff --git a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
index 8c95637a5e..b0a38e8aca 100644
--- a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
+++ b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
@@ -41,7 +41,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import static org.apache.nifi.processors.aws.region.RegionUtil.CUSTOM_REGION;
import static org.apache.nifi.processors.aws.region.RegionUtil.REGION;
@@ -321,7 +320,7 @@ public abstract class AbstractS3Processor extends AbstractAwsSyncProcessor<S3Cli
}
}
- @ConnectorMethod(name="getAvailableRegions", description="Returns the list of available AWS regions")
+ @ConnectorMethod(name = "getAvailableRegions", description = "Returns the list of available AWS regions")
public List<String> getAvailableRegions() {
return RegionUtil.getAwsRegionAllowableValues().stream()
.map(AllowableValue::getValue)
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java
index 0fb38dabab..14c50d45ba 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java
@@ -32,7 +32,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
import static org.apache.nifi.kafka.processors.ConsumeKafka.CONNECTION_SERVICE;
import static org.apache.nifi.kafka.processors.ConsumeKafka.GROUP_ID;
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
index 61c71e0a03..9ecf11b6f7 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
@@ -304,8 +304,8 @@ public class Kafka3ConnectionService extends AbstractControllerService implement
}
@ConnectorMethod(
- name="listTopicNames",
- description="Returns a list of topic names available in the Kafka cluster",
+ name = "listTopicNames",
+ description = "Returns a list of topic names available in the Kafka cluster",
arguments = {
@MethodArgument(name = "context", type = ConfigurationContext.class, description = "The configuration context that specifies connectivity details")
})
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
index 25c6f03da9..57efd27d08 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
@@ -99,7 +99,6 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
public class StandardControllerServiceNode extends AbstractComponentNode implements ControllerServiceNode {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java
index d09daf007a..0a773bd90b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/remote/StandardRemoteProcessGroup.java
@@ -16,7 +16,6 @@
*/
package org.apache.nifi.remote;
-import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
index 66cc653f3b..2a3e804551 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/MutableConnectorConfigurationContext.java
@@ -18,7 +18,6 @@
package org.apache.nifi.components.connector;
import java.util.List;
-import java.util.Map;
public interface MutableConnectorConfigurationContext extends ConnectorConfigurationContext {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
index 24648d091b..d13f642ca2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
@@ -25,7 +25,6 @@ import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
-import java.util.Map;
public class GhostConnector implements Connector {
private final String identifier;
@@ -74,7 +73,8 @@ public class GhostConnector implements Connector {
}
@Override
- public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext, final ConnectorValidationContext connectorValidationContext) {
+ public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext,
+ final ConnectorValidationContext connectorValidationContext) {
return validationResults;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
index f071358b48..d95d7c9026 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
@@ -24,7 +24,6 @@ import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.CountDownLatch;
public class BlockingConnector implements Connector {
@@ -72,7 +71,8 @@ public class BlockingConnector implements Connector {
}
@Override
- public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext, final ConnectorValidationContext connectorValidationContext) {
+ public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext,
+ final ConnectorValidationContext connectorValidationContext) {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
index 4d654cc606..7d7e6b1ebb 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
@@ -25,7 +25,6 @@ import org.apache.nifi.flow.VersionedExternalFlow;
import java.time.Duration;
import java.util.List;
-import java.util.Map;
public class SleepingConnector implements Connector {
private final Duration sleepDuration;
@@ -67,11 +66,11 @@ public class SleepingConnector implements Connector {
}
@Override
- public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext, final ConnectorValidationContext connectorValidationContext) {
+ public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext,
+ final ConnectorValidationContext connectorValidationContext) {
return List.of();
}
-
@Override
public List<ConfigurationStep> getConfigurationSteps(final FlowContext fLowContext) {
return List.of();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index 28cf6e099f..fb37e04e29 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -604,7 +604,9 @@ public class TestStandardConnectorNode {
}
@Override
- public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext, final ConnectorValidationContext connectorValidationContext) {
+ public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext,
+ final ConnectorValidationContext connectorValidationContext) {
+
final ValidationResult invalidResult = new ValidationResult.Builder()
.subject("Test Property")
.valid(false)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
index fdad53c9ed..0cf39390e1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
@@ -31,7 +31,6 @@ import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
-import java.util.Map;
/**
* A simple no-op Connector implementation for testing purposes.
@@ -87,7 +86,8 @@ public class NopConnector implements Connector {
}
@Override
- public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext, final ConnectorValidationContext connectorValidationContext) {
+ public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext,
+ final ConnectorValidationContext connectorValidationContext) {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
index cd4b803bed..68b13e00f9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
@@ -31,7 +31,6 @@ import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.VersionedExternalFlow;
import java.util.List;
-import java.util.Map;
@Tags({"test", "connector"})
public class DummyConnector implements Connector {
@@ -98,7 +97,8 @@ public class DummyConnector implements Connector {
}
@Override
- public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext, final ConnectorValidationContext connectorValidationContext) {
+ public List<ValidationResult> validateConfigurationStep(final ConfigurationStep configurationStep, final ConnectorConfigurationContext connectorConfigurationContext,
+ final ConnectorValidationContext connectorValidationContext) {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java
index d598506bcb..ec61f15f77 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java
@@ -34,7 +34,6 @@ import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.RequiredPermission;
-import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.connectable.Port;
@@ -76,7 +75,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 21184152f5..de303d5c33 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -162,7 +162,6 @@ import org.apache.nifi.parameter.ParameterGroupConfiguration;
import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.parameter.ParameterProvider;
import org.apache.nifi.parameter.ParameterReferenceManager;
-import org.apache.nifi.parameter.StandardParameterContext;
import org.apache.nifi.parameter.StandardParameterContext.Builder;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processor.VerifiableProcessor;
(nifi) 37/40: NIFI-15481: Updated ConnectorActions to reflect that starting is not … (#10782)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 6af93e06f57de23e6a3c94a13d5fae3dfa8bb150
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Wed Jan 21 12:49:44 2026 -0500
NIFI-15481: Updated ConnectorActions to reflect that starting is not … (#10782)
* NIFI-15481: Updated ConnectorActions to reflect that starting is not allowed when invalid. Also updated ValidationState to ensure that we don't return any ValidationResult with a valid flag of true as a 'validation error'.
* NIFI-15481: Addressed review feedback
---
.../components/validation/ValidationState.java | 16 ++++++++--
.../nifi/controller/AbstractComponentNode.java | 12 +++++---
.../connector/StandardConnectorNode.java | 36 ++++++++++------------
3 files changed, 38 insertions(+), 26 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/validation/ValidationState.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/validation/ValidationState.java
index 184f891d45..d1efd43ab9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/validation/ValidationState.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/validation/ValidationState.java
@@ -20,14 +20,26 @@ package org.apache.nifi.components.validation;
import org.apache.nifi.components.ValidationResult;
import java.util.Collection;
+import java.util.Collections;
public class ValidationState {
private final ValidationStatus status;
private final Collection<ValidationResult> validationErrors;
- public ValidationState(final ValidationStatus status, final Collection<ValidationResult> validationErrors) {
+ public ValidationState(final ValidationStatus status, final Collection<ValidationResult> validationResults) {
this.status = status;
- this.validationErrors = validationErrors;
+ // Ensure that if we are provided any valid results, they are filtered out because we only want to store validation failures.
+ this.validationErrors = removeValidResults(validationResults);
+ }
+
+ private Collection<ValidationResult> removeValidResults(final Collection<ValidationResult> validationResults) {
+ if (validationResults.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return validationResults.stream()
+ .filter(vr -> !vr.isValid())
+ .toList();
}
public ValidationStatus getStatus() {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
index ca7c6f9065..ecaa4d3f31 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
@@ -867,13 +867,15 @@ public abstract class AbstractComponentNode implements ComponentNode {
@Override
public ValidationState performValidation(final ValidationContext validationContext) {
- final Collection<ValidationResult> results;
+ final Collection<ValidationResult> allResults;
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getComponent().getClass(), getIdentifier())) {
- results = computeValidationErrors(validationContext);
+ allResults = computeValidationErrors(validationContext);
}
- final ValidationStatus status = results.isEmpty() ? ValidationStatus.VALID : ValidationStatus.INVALID;
- final ValidationState validationState = new ValidationState(status, results);
+ final Collection<ValidationResult> invalidResults = allResults.isEmpty() ? Collections.emptyList()
+ : allResults.stream().filter(result -> !result.isValid()).toList();
+ final ValidationStatus status = invalidResults.isEmpty() ? ValidationStatus.VALID : ValidationStatus.INVALID;
+ final ValidationState validationState = new ValidationState(status, invalidResults);
return validationState;
}
@@ -892,7 +894,7 @@ public abstract class AbstractComponentNode implements ComponentNode {
}
protected Collection<ValidationResult> computeValidationErrors(final ValidationContext validationContext) {
- Throwable failureCause = null;
+ Throwable failureCause;
try {
if (!sensitiveDynamicPropertyNames.get().isEmpty() && !isSupportsSensitiveDynamicProperties()) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index 2bb5793432..afb6e37bd9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -809,7 +809,6 @@ public class StandardConnectorNode implements ConnectorNode {
final ValidationState state = performValidation();
if (state.getStatus() == ValidationStatus.INVALID) {
final List<String> validationFailureExplanations = state.getValidationErrors().stream()
- .filter(result -> !result.isValid())
.map(ValidationResult::getExplanation)
.toList();
@@ -894,6 +893,7 @@ public class StandardConnectorNode implements ConnectorNode {
if (currentState == ConnectorState.UPDATED || currentState == ConnectorState.UPDATE_FAILED) {
return !hasActiveThread(getActiveFlowContext().getManagedProcessGroup());
}
+
return false;
}
@@ -905,8 +905,16 @@ public class StandardConnectorNode implements ConnectorNode {
allowed = false;
reason = "Connector is not stopped";
} else {
- allowed = true;
- reason = null;
+ final Collection<ValidationResult> validationResults = getValidationErrors();
+ if (validationResults.isEmpty()) {
+ allowed = true;
+ reason = null;
+ } else {
+ allowed = false;
+ reason = "Connector is not valid: " + validationResults.stream()
+ .map(ValidationResult::getExplanation)
+ .collect(Collectors.joining("; "));
+ }
}
return new StandardConnectorAction("START", "Start the connector", allowed, reason);
@@ -954,24 +962,14 @@ public class StandardConnectorNode implements ConnectorNode {
}
private ConnectorAction createPurgeFlowFilesAction(final boolean stopped, final boolean dataQueued) {
- final boolean allowed;
- final String reason;
-
- if (!stopped) {
- allowed = false;
- reason = "Connector must be stopped";
- } else if (!dataQueued) {
- allowed = false;
- reason = "No data is queued";
- } else {
- allowed = true;
- reason = null;
- }
-
- return new StandardConnectorAction("PURGE_FLOWFILES", "Purge all FlowFiles from the connector, dropping all data without processing it", allowed, reason);
+ return createDataQueuedAction(stopped, dataQueued, "PURGE_FLOWFILES", "Purge all FlowFiles from the connector, dropping all data without processing it");
}
private ConnectorAction createDrainFlowFilesAction(final boolean stopped, final boolean dataQueued) {
+ return createDataQueuedAction(stopped, dataQueued, "DRAIN_FLOWFILES", "Process data that is currently in the flow but do not ingest any additional data");
+ }
+
+ private static ConnectorAction createDataQueuedAction(final boolean stopped, final boolean dataQueued, final String actionName, final String description) {
final boolean allowed;
final String reason;
@@ -986,7 +984,7 @@ public class StandardConnectorNode implements ConnectorNode {
reason = null;
}
- return new StandardConnectorAction("DRAIN_FLOWFILES", "Process data that is currently in the flow but do not ingest any additional data", allowed, reason);
+ return new StandardConnectorAction(actionName, description, allowed, reason);
}
private ConnectorAction createApplyUpdatesAction(final ConnectorState currentState) {
(nifi) 06/40: NIFI-15323: Adding operate permissions to the ConnectorEntity. (#10625)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 798594c8ef96de959271fd95ddaef49a6114ad45
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Wed Dec 10 14:55:50 2025 -0500
NIFI-15323: Adding operate permissions to the ConnectorEntity. (#10625)
This closes #10625
---
.../nifi/web/api/entity/ConnectorEntity.java | 8 +++++---
.../apache/nifi/web/StandardNiFiServiceFacade.java | 24 +++++++++++++++-------
.../org/apache/nifi/web/api/dto/EntityFactory.java | 4 +++-
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorEntity.java
index 1e533823fa..0cf02aaa00 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorEntity.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorEntity.java
@@ -28,6 +28,7 @@ import org.apache.nifi.web.api.dto.status.ConnectorStatusDTO;
public class ConnectorEntity extends ComponentEntity implements Permissible<ConnectorDTO>, OperationPermissible {
private ConnectorDTO component;
private ConnectorStatusDTO status;
+ private PermissionsDTO operatePermissions;
@Override
@Schema(description = "The Connector DTO")
@@ -40,14 +41,15 @@ public class ConnectorEntity extends ComponentEntity implements Permissible<Conn
this.component = component;
}
+ @Schema(description = "The permissions for this component operations.")
@Override
public PermissionsDTO getOperatePermissions() {
- return getPermissions();
+ return operatePermissions;
}
@Override
- public void setOperatePermissions(final PermissionsDTO permissions) {
- setPermissions(permissions);
+ public void setOperatePermissions(final PermissionsDTO operatePermissions) {
+ this.operatePermissions = operatePermissions;
}
@Schema(description = "The status of the connector.")
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 1fa7e93076..5cc7dd046f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -3494,7 +3494,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ConnectorNode connector = connectorDAO.getConnector(snapshot.getComponent().getId());
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connector);
- return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(connector));
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
}
@Override
@@ -3503,7 +3504,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(node.getIdentifier()));
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
- return entityFactory.createConnectorEntity(dto, revision, permissions);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(node));
+ return entityFactory.createConnectorEntity(dto, revision, permissions, operatePermissions);
}).collect(Collectors.toSet());
}
@@ -3513,7 +3515,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(node.getIdentifier()));
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
- return entityFactory.createConnectorEntity(dto, revision, permissions);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(node));
+ return entityFactory.createConnectorEntity(dto, revision, permissions, operatePermissions);
}
@Override
@@ -3539,7 +3542,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
- return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(node));
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
}
@Override
@@ -3565,7 +3569,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ConnectorDTO dto = snapshot.getComponent();
final PermissionsDTO permissions = new PermissionsDTO();
permissions.setCanRead(Boolean.FALSE);
- return entityFactory.createConnectorEntity(dto, dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ permissions.setCanWrite(Boolean.FALSE);
+ final PermissionsDTO operatePermissions = new PermissionsDTO();
+ operatePermissions.setCanRead(Boolean.FALSE);
+ operatePermissions.setCanWrite(Boolean.FALSE);
+ return entityFactory.createConnectorEntity(dto, dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
}
@Override
@@ -3590,7 +3598,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
- return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(node));
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
}
@Override
@@ -3648,7 +3657,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
- return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(node));
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
}
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
index ab4f27e2f8..9ebe1fced4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
@@ -130,11 +130,13 @@ public final class EntityFactory {
public ConnectorEntity createConnectorEntity(final ConnectorDTO dto,
final RevisionDTO revision,
- final PermissionsDTO permissions) {
+ final PermissionsDTO permissions,
+ final PermissionsDTO operatePermissions) {
final ConnectorEntity entity = new ConnectorEntity();
entity.setRevision(revision);
if (dto != null) {
entity.setPermissions(permissions);
+ entity.setOperatePermissions(operatePermissions);
entity.setId(dto.getId());
if (permissions != null && permissions.getCanRead()) {
entity.setComponent(dto);
(nifi) 09/40: NIFI-15330: Allow GhostConnector to be updated (#10632)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit a470e8b9fa43768d876205955301c202be6b8e7c
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Thu Dec 11 13:51:58 2025 -0500
NIFI-15330: Allow GhostConnector to be updated (#10632)
---
.../main/java/org/apache/nifi/components/connector/GhostConnector.java | 3 ---
1 file changed, 3 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
index f936c24446..5f37546e51 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
@@ -61,7 +61,6 @@ public class GhostConnector implements Connector {
@Override
public void start(final FlowContext activeContext) throws FlowUpdateException {
- throw new UnsupportedOperationException("Cannot start a Ghosted Connector");
}
@Override
@@ -90,7 +89,6 @@ public class GhostConnector implements Connector {
@Override
public void prepareForUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
- throw new UnsupportedOperationException("Cannot update a Ghosted Connector");
}
@Override
@@ -99,7 +97,6 @@ public class GhostConnector implements Connector {
@Override
public void applyUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
- throw new UnsupportedOperationException("Cannot update a Ghosted Connector");
}
@Override
(nifi) 40/40: NIFI-15461: Added ability to initiate drainage of Connector's FlowFiles and calce; added tests to verify; some bug fixes (#10767)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 135d3f0d70a7b49c6189db084f400cb0ef748d27
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Wed Jan 21 14:20:07 2026 -0500
NIFI-15461: Added ability to initiate drainage of Connector's FlowFiles and calce; added tests to verify; some bug fixes (#10767)
---
.../http/StandardHttpResponseMapper.java | 2 +
.../endpoints/ConnectorStatusEndpointMerger.java | 86 ++++++++
.../ClusteredConnectorRequestReplicator.java | 22 +-
.../cluster/manager/ConnectorEntityMerger.java | 29 ++-
.../apache/nifi/cluster/manager/StatusMerger.java | 15 ++
.../cluster/manager/ConnectorEntityMergerTest.java | 30 +++
.../nifi/components/connector/ConnectorNode.java | 12 ++
.../connector/StandardConnectorNode.java | 83 +++++++-
.../connector/StandardConnectorRepository.java | 22 +-
.../components/connector/StandardFlowContext.java | 5 +
.../connector/TestStandardConnectorNode.java | 118 +++++++++++
.../org/apache/nifi/web/NiFiServiceFacade.java | 8 +
.../apache/nifi/web/StandardNiFiServiceFacade.java | 57 ++++++
.../org/apache/nifi/web/api/ConnectorResource.java | 149 ++++++++++++++
.../java/org/apache/nifi/web/dao/ConnectorDAO.java | 6 +
.../nifi/web/dao/impl/StandardConnectorDAO.java | 18 ++
.../apache/nifi/web/api/TestConnectorResource.java | 62 +++++-
.../tests/system/DataQueuingConnector.java | 3 +-
...nnector.java => GatedDataQueuingConnector.java} | 73 ++++++-
.../processors/tests/system/TerminateFlowFile.java | 28 +++
.../org.apache.nifi.components.connector.Connector | 1 +
.../apache/nifi/tests/system/NiFiClientUtil.java | 24 +++
.../org/apache/nifi/tests/system/NiFiSystemIT.java | 23 +++
.../connectors/ClusteredConnectorDrainIT.java | 228 +++++++++++++++++++++
.../tests/system/connectors/ConnectorDrainIT.java | 178 ++++++++++++++++
.../nifi/toolkit/client/ConnectorClient.java | 44 ++++
.../toolkit/client/impl/JerseyConnectorClient.java | 69 +++++++
27 files changed, 1357 insertions(+), 38 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
index 0eefb8b763..efa017ae5f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
@@ -30,6 +30,7 @@ import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorEndpointMerg
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorFlowEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorPropertyGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorPropertyGroupNamesEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorStatusEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ControllerBulletinsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ControllerConfigurationEndpointMerger;
@@ -146,6 +147,7 @@ public class StandardHttpResponseMapper implements HttpResponseMapper {
endpointMergers.add(new ProcessorRunStatusDetailsEndpointMerger());
endpointMergers.add(new ConnectorEndpointMerger());
endpointMergers.add(new ConnectorsEndpointMerger());
+ endpointMergers.add(new ConnectorStatusEndpointMerger());
endpointMergers.add(new ConnectorFlowEndpointMerger());
endpointMergers.add(new ConnectorPropertyGroupEndpointMerger());
endpointMergers.add(new ConnectorPropertyGroupNamesEndpointMerger());
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorStatusEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorStatusEndpointMerger.java
new file mode 100644
index 0000000000..bff04b3fe7
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorStatusEndpointMerger.java
@@ -0,0 +1,86 @@
+/*
+ * 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.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.manager.ComponentEntityStatusMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.StatusMerger;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.status.NodeProcessGroupStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
+import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class ConnectorStatusEndpointMerger extends AbstractSingleEntityEndpoint<ProcessGroupStatusEntity> implements ComponentEntityStatusMerger<ProcessGroupStatusDTO> {
+ public static final Pattern CONNECTOR_STATUS_URI_PATTERN = Pattern.compile("/nifi-api/connectors/[a-f0-9\\-]{36}/status");
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return "GET".equalsIgnoreCase(method) && CONNECTOR_STATUS_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ protected Class<ProcessGroupStatusEntity> getEntityClass() {
+ return ProcessGroupStatusEntity.class;
+ }
+
+ @Override
+ protected void mergeResponses(final ProcessGroupStatusEntity clientEntity, final Map<NodeIdentifier, ProcessGroupStatusEntity> entityMap,
+ final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+ final ProcessGroupStatusDTO mergedProcessGroupStatus = clientEntity.getProcessGroupStatus();
+ mergedProcessGroupStatus.setNodeSnapshots(new ArrayList<>());
+
+ final NodeIdentifier selectedNodeId = entityMap.entrySet().stream()
+ .filter(e -> e.getValue() == clientEntity)
+ .map(Map.Entry::getKey)
+ .findFirst()
+ .orElse(null);
+
+ final NodeProcessGroupStatusSnapshotDTO selectedNodeSnapshot = new NodeProcessGroupStatusSnapshotDTO();
+ selectedNodeSnapshot.setStatusSnapshot(mergedProcessGroupStatus.getAggregateSnapshot().clone());
+ selectedNodeSnapshot.setAddress(selectedNodeId.getApiAddress());
+ selectedNodeSnapshot.setApiPort(selectedNodeId.getApiPort());
+ selectedNodeSnapshot.setNodeId(selectedNodeId.getId());
+
+ mergedProcessGroupStatus.getNodeSnapshots().add(selectedNodeSnapshot);
+
+ for (final Map.Entry<NodeIdentifier, ProcessGroupStatusEntity> entry : entityMap.entrySet()) {
+ final NodeIdentifier nodeId = entry.getKey();
+ final ProcessGroupStatusEntity nodeProcessGroupStatusEntity = entry.getValue();
+ final ProcessGroupStatusDTO nodeProcessGroupStatus = nodeProcessGroupStatusEntity.getProcessGroupStatus();
+ if (nodeProcessGroupStatus == mergedProcessGroupStatus) {
+ continue;
+ }
+
+ mergeStatus(mergedProcessGroupStatus, clientEntity.getCanRead(), nodeProcessGroupStatus, nodeProcessGroupStatusEntity.getCanRead(), nodeId);
+ }
+ }
+
+ @Override
+ public void mergeStatus(final ProcessGroupStatusDTO clientStatus, final boolean clientStatusReadablePermission,
+ final ProcessGroupStatusDTO status, final boolean statusReadablePermission,
+ final NodeIdentifier statusNodeIdentifier) {
+ StatusMerger.merge(clientStatus, clientStatusReadablePermission, status, statusReadablePermission,
+ statusNodeIdentifier.getId(), statusNodeIdentifier.getApiAddress(), statusNodeIdentifier.getApiPort());
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java
index 8fdc58dd35..a3db59cf4f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java
@@ -24,11 +24,13 @@ import jakarta.ws.rs.core.Response.StatusType;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.cluster.coordination.ClusterCoordinator;
+import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.components.connector.ConnectorRequestReplicator;
import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.Entity;
import java.io.IOException;
import java.net.URI;
@@ -55,14 +57,26 @@ public class ClusteredConnectorRequestReplicator implements ConnectorRequestRepl
public ConnectorState getState(final String connectorId) throws IOException {
final RequestReplicator requestReplicator = getRequestReplicator();
final NiFiUser nodeUser = getNodeUser();
- final AsyncClusterResponse asyncResponse = requestReplicator.replicate(nodeUser, GET, URI.create(replicationScheme + "://localhost/nifi-api/connectors/" + connectorId), Map.of(), Map.of());
+ final URI uri = URI.create(replicationScheme + "://localhost/nifi-api/connectors/" + connectorId);
+ final AsyncClusterResponse asyncResponse = requestReplicator.replicate(nodeUser, GET, uri, Map.of(), Map.of());
+
try {
- final Response response = asyncResponse.awaitMergedResponse().getClientResponse();
+ final NodeResponse mergedNodeResponse = asyncResponse.awaitMergedResponse();
+ final Response response = mergedNodeResponse.getClientResponse();
verifyResponse(response.getStatusInfo(), connectorId);
- final ConnectorEntity connectorEntity = response.readEntity(ConnectorEntity.class);
- final String stateName = connectorEntity.getComponent().getState();
+ // Use the merged/updated entity if available, otherwise fall back to reading from the raw response.
+ // The updatedEntity contains the properly merged state from all nodes, while readEntity() would
+ // only return the state from whichever single node was selected as the "client response".
+ final ConnectorEntity connectorEntity;
+ final Entity updatedEntity = mergedNodeResponse.getUpdatedEntity();
+ if (updatedEntity instanceof ConnectorEntity mergedConnectorEntity) {
+ connectorEntity = mergedConnectorEntity;
+ } else {
+ connectorEntity = response.readEntity(ConnectorEntity.class);
+ }
+ final String stateName = connectorEntity.getComponent().getState();
try {
return ConnectorState.valueOf(stateName);
} catch (final IllegalArgumentException e) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java
index 5f880dfc3f..7ef7fdf76b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java
@@ -17,6 +17,7 @@
package org.apache.nifi.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorDTO;
@@ -26,7 +27,6 @@ import java.util.HashMap;
import java.util.Map;
public class ConnectorEntityMerger {
-
/**
* Merges the ConnectorEntity responses.
*
@@ -43,6 +43,16 @@ public class ConnectorEntityMerger {
}
mergeDtos(clientDto, dtoMap);
+
+ mergeStatus(clientEntity, entityMap);
+ }
+
+ private static void mergeStatus(final ConnectorEntity clientEntity, final Map<NodeIdentifier, ConnectorEntity> entityMap) {
+ for (final ConnectorEntity nodeEntity : entityMap.values()) {
+ if (nodeEntity != clientEntity && nodeEntity != null) {
+ StatusMerger.merge(clientEntity.getStatus(), nodeEntity.getStatus());
+ }
+ }
}
private static void mergeDtos(final ConnectorDTO clientDto, final Map<NodeIdentifier, ConnectorDTO> dtoMap) {
@@ -89,13 +99,16 @@ public class ConnectorEntityMerger {
if (state == null) {
return 0;
}
- return switch (state) {
- case "UPDATE_FAILED" -> 6;
- case "PREPARING_FOR_UPDATE" -> 5;
- case "UPDATING" -> 4;
- case "UPDATED" -> 3;
- case "STARTING", "STOPPING" -> 2;
- default -> 1; // RUNNING, STOPPED, DISABLED
+
+ final ConnectorState connectorState = ConnectorState.valueOf(state);
+ return switch (connectorState) {
+ case UPDATE_FAILED -> 7;
+ case PREPARING_FOR_UPDATE -> 6;
+ case UPDATING -> 5;
+ case DRAINING, PURGING -> 4;
+ case UPDATED -> 3;
+ case STARTING, STOPPING -> 2;
+ default -> 1; // RUNNING, STOPPED
};
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java
index f238dc1049..5b403e95f8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java
@@ -43,6 +43,7 @@ import org.apache.nifi.web.api.dto.diagnostics.JVMSystemDiagnosticsSnapshotDTO;
import org.apache.nifi.web.api.dto.status.ConnectionStatusDTO;
import org.apache.nifi.web.api.dto.status.ConnectionStatusPredictionsSnapshotDTO;
import org.apache.nifi.web.api.dto.status.ConnectionStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.ConnectorStatusDTO;
import org.apache.nifi.web.api.dto.status.ControllerServiceStatusDTO;
import org.apache.nifi.web.api.dto.status.ControllerStatusDTO;
import org.apache.nifi.web.api.dto.status.FlowAnalysisRuleStatusDTO;
@@ -1084,4 +1085,18 @@ public class StatusMerger {
target.setValidationStatus(ValidationStatus.INVALID.name());
}
}
+
+ public static void merge(final ConnectorStatusDTO target, final ConnectorStatusDTO toMerge) {
+ if (target == null || toMerge == null) {
+ return;
+ }
+
+ target.setActiveThreadCount(target.getActiveThreadCount() + toMerge.getActiveThreadCount());
+
+ if (ValidationStatus.VALIDATING.name().equalsIgnoreCase(toMerge.getValidationStatus())) {
+ target.setValidationStatus(ValidationStatus.VALIDATING.name());
+ } else if (ValidationStatus.INVALID.name().equalsIgnoreCase(toMerge.getValidationStatus())) {
+ target.setValidationStatus(ValidationStatus.INVALID.name());
+ }
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorEntityMergerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorEntityMergerTest.java
index da0ed151f2..21f351b7a9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorEntityMergerTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorEntityMergerTest.java
@@ -25,6 +25,7 @@ import org.apache.nifi.web.api.dto.ConnectorPropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.PermissionsDTO;
import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.dto.status.ConnectorStatusDTO;
import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.junit.jupiter.api.Test;
@@ -130,6 +131,22 @@ class ConnectorEntityMergerTest {
assertEquals(2, config.getConfigurationStepConfigurations().size());
}
+ @Test
+ void testMergeConnectorStatusValidationStatusPriority() {
+ final ConnectorEntity clientEntity = createConnectorEntityWithStatus("connector1", "RUNNING", 1, "VALID");
+ final ConnectorEntity node1Entity = createConnectorEntityWithStatus("connector1", "RUNNING", 1, "VALIDATING");
+ final ConnectorEntity node2Entity = createConnectorEntityWithStatus("connector1", "RUNNING", 1, "VALID");
+
+ final Map<NodeIdentifier, ConnectorEntity> entityMap = new HashMap<>();
+ entityMap.put(getNodeIdentifier("client", 8000), clientEntity);
+ entityMap.put(getNodeIdentifier("node1", 8001), node1Entity);
+ entityMap.put(getNodeIdentifier("node2", 8002), node2Entity);
+
+ ConnectorEntityMerger.merge(clientEntity, entityMap);
+
+ assertEquals("VALIDATING", clientEntity.getStatus().getValidationStatus());
+ }
+
private NodeIdentifier getNodeIdentifier(final String id, final int port) {
return new NodeIdentifier(id, "localhost", port, "localhost", port + 1, "localhost", port + 2, port + 3, true);
}
@@ -153,6 +170,19 @@ class ConnectorEntityMergerTest {
return entity;
}
+ private ConnectorEntity createConnectorEntityWithStatus(final String id, final String state, final int activeThreadCount, final String validationStatus) {
+ final ConnectorEntity entity = createConnectorEntity(id, state);
+
+ final ConnectorStatusDTO status = new ConnectorStatusDTO();
+ status.setId(id);
+ status.setRunStatus(state);
+ status.setActiveThreadCount(activeThreadCount);
+ status.setValidationStatus(validationStatus);
+ entity.setStatus(status);
+
+ return entity;
+ }
+
private ConnectorEntity createConnectorEntityWithConfig(final String id, final String state,
final Map<String, List<String>> propertyAllowableValues) {
final ConnectorEntity entity = createConnectorEntity(id, state);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index bb3bdf6e2d..8a39f4b66a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -199,6 +199,18 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
*/
Future<Void> drainFlowFiles();
+ /**
+ * Cancels the draining of FlowFiles that is currently in progress.
+ * @throws IllegalStateException if the Connector is not currently draining FlowFiles
+ */
+ void cancelDrainFlowFiles();
+
+ /**
+ * Verifies that the Connector can cancel draining FlowFiles.
+ * @throws IllegalStateException if not in a state where draining FlowFiles can be cancelled
+ */
+ void verifyCancelDrainFlowFiles() throws IllegalStateException;
+
/**
* Purges all FlowFiles from the Connector, immediately dropping the data.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index afb6e37bd9..471fbe10ae 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -41,9 +41,13 @@ import org.apache.nifi.controller.queue.DropFlowFileStatus;
import org.apache.nifi.controller.queue.QueueSize;
import org.apache.nifi.engine.FlowEngine;
import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.flow.ScheduledState;
import org.apache.nifi.flow.VersionedConfigurationStep;
import org.apache.nifi.flow.VersionedConnectorValueReference;
+import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.nar.ExtensionManager;
@@ -95,6 +99,7 @@ public class StandardConnectorNode implements ConnectorNode {
private final ConnectorValidationTrigger validationTrigger;
private final boolean extensionMissing;
private volatile boolean triggerValidation = true;
+ private final AtomicReference<CompletableFuture<Void>> drainFutureRef = new AtomicReference<>();
private volatile FrameworkFlowContext workingFlowContext;
@@ -398,6 +403,7 @@ public class StandardConnectorNode implements ConnectorNode {
@Override
public Future<Void> stop(final FlowEngine scheduler) {
+ logger.info("Stopping {}", this);
final CompletableFuture<Void> stopCompleteFuture = new CompletableFuture<>();
stateTransition.setDesiredState(ConnectorState.STOPPED);
@@ -406,7 +412,7 @@ public class StandardConnectorNode implements ConnectorNode {
while (!stateUpdated) {
final ConnectorState currentState = getCurrentState();
if (currentState == ConnectorState.STOPPED) {
- logger.debug("{} is already {}; will not attempt to stop", this, currentState);
+ logger.info("{} is already stopped.", this);
stopCompleteFuture.complete(null);
return stopCompleteFuture;
}
@@ -430,8 +436,24 @@ public class StandardConnectorNode implements ConnectorNode {
requireStopped("drain FlowFiles", ConnectorState.DRAINING);
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connectorDetails.getConnector().getClass(), getIdentifier())) {
- final CompletableFuture<Void> future = connectorDetails.getConnector().drainFlowFiles(activeFlowContext);
- final CompletableFuture<Void> stateUpdateFuture = future.whenComplete((result, failureCause) -> stateTransition.setCurrentState(ConnectorState.STOPPED));
+ getComponentLog().info("Draining FlowFiles from {}", this);
+ final CompletableFuture<Void> drainFuture = connectorDetails.getConnector().drainFlowFiles(activeFlowContext);
+ drainFutureRef.set(drainFuture);
+
+ final CompletableFuture<Void> stateUpdateFuture = drainFuture.whenComplete((result, failureCause) -> {
+ drainFutureRef.set(null);
+ logger.info("Successfully drained FlowFiles from {}; ensuring all components are stopped.", this);
+
+ try {
+ connectorDetails.getConnector().stop(activeFlowContext);
+ } catch (final Exception e) {
+ logger.warn("Failed to stop {} after draining FlowFiles", this, e);
+ }
+
+ stateTransition.setCurrentState(ConnectorState.STOPPED);
+ logger.info("All components of {} are now stopped after draining FlowFiles.", this);
+ });
+
return stateUpdateFuture;
} catch (final Throwable t) {
stateTransition.setCurrentState(ConnectorState.STOPPED);
@@ -439,6 +461,30 @@ public class StandardConnectorNode implements ConnectorNode {
}
}
+ @Override
+ public void cancelDrainFlowFiles() {
+ final Future<Void> future = this.drainFutureRef.getAndSet(null);
+ if (future == null) {
+ logger.debug("No active drain to cancel for {}; drain may have already completed", this);
+ return;
+ }
+
+ future.cancel(true);
+ logger.info("Cancelled draining of FlowFiles for {}", this);
+ }
+
+ @Override
+ public void verifyCancelDrainFlowFiles() throws IllegalStateException {
+ final ConnectorState state = getCurrentState();
+
+ // Allow if we're currently draining or if we're stopped; if stopped the cancel drain action will be a no-op
+ // but we don't want to throw an IllegalStateException in that case because doing so would mean that if one
+ // node in the cluster is stopped while another is draining we cannot cancel the drain.
+ if (state != ConnectorState.DRAINING && state != ConnectorState.STOPPED) {
+ throw new IllegalStateException("Cannot cancel draining of FlowFiles for " + this + " because its current state is " + state + "; it must be DRAINING.");
+ }
+ }
+
@Override
public Future<Void> purgeFlowFiles(final String requestor) {
requireStopped("purge FlowFiles", ConnectorState.PURGING);
@@ -610,6 +656,9 @@ public class StandardConnectorNode implements ConnectorNode {
logger.info("{} has no initial flow to load", this);
} else {
logger.info("Loading initial flow for {}", this);
+ // Update all RUNNING components to ENABLED before applying the initial flow so that components
+ // are not started before being configured.
+ stopComponents(initialFlow.getFlowContents());
initializationContext.updateFlow(activeFlowContext, initialFlow);
}
@@ -617,6 +666,24 @@ public class StandardConnectorNode implements ConnectorNode {
recreateWorkingFlowContext();
}
+ private void stopComponents(final VersionedProcessGroup group) {
+ for (final VersionedProcessor processor : group.getProcessors()) {
+ if (processor.getScheduledState() == ScheduledState.RUNNING) {
+ processor.setScheduledState(ScheduledState.ENABLED);
+ }
+ }
+
+ for (final VersionedControllerService service : group.getControllerServices()) {
+ if (service.getScheduledState() == ScheduledState.RUNNING) {
+ service.setScheduledState(ScheduledState.ENABLED);
+ }
+ }
+
+ for (final VersionedProcessGroup childGroup : group.getProcessGroups()) {
+ stopComponents(childGroup);
+ }
+ }
+
private void recreateWorkingFlowContext() {
destroyWorkingContext();
workingFlowContext = flowContextFactory.createWorkingFlowContext(identifier,
@@ -879,6 +946,7 @@ public class StandardConnectorNode implements ConnectorNode {
actions.add(createDiscardWorkingConfigAction());
actions.add(createPurgeFlowFilesAction(stopped, dataQueued));
actions.add(createDrainFlowFilesAction(stopped, dataQueued));
+ actions.add(createCancelDrainFlowFilesAction(currentState == ConnectorState.DRAINING));
actions.add(createApplyUpdatesAction(currentState));
actions.add(createDeleteAction(stopped, dataQueued));
@@ -987,6 +1055,15 @@ public class StandardConnectorNode implements ConnectorNode {
return new StandardConnectorAction(actionName, description, allowed, reason);
}
+ private ConnectorAction createCancelDrainFlowFilesAction(final boolean draining) {
+ if (draining) {
+ return new StandardConnectorAction("CANCEL_DRAIN_FLOWFILES", "Cancel the ongoing drain of FlowFiles", true, null);
+ }
+
+ return new StandardConnectorAction("CANCEL_DRAIN_FLOWFILES", "Cancel the ongoing drain of FlowFiles", false,
+ "Connector is not currently draining FlowFiles");
+ }
+
private ConnectorAction createApplyUpdatesAction(final ConnectorState currentState) {
final boolean allowed;
final String reason;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index d47cb4ed2e..40b39694fd 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -31,7 +31,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -107,11 +106,14 @@ public class StandardConnectorRepository implements ConnectorRepository {
@Override
public void applyUpdate(final ConnectorNode connector, final ConnectorUpdateContext context) throws FlowUpdateException {
final ConnectorState initialDesiredState = connector.getDesiredState();
+ logger.info("Applying update to Connector {}", connector);
// Transition the connector's state to PREPARING_FOR_UPDATE before starting the background process.
// This allows us to ensure that if we poll and see the state in the same state it was in before that
// we know the update has already completed (successfully or otherwise).
+ logger.debug("Transitioning {} to PREPARING_FOR_UPDATE state before applying update", connector);
connector.transitionStateForUpdating();
+ logger.debug("{} is now in PREPARING_FOR_UPDATE state", connector);
// Update connector in a background thread. This will handle transitioning the Connector state appropriately
// so that it's clear when the update has completed.
@@ -121,28 +123,24 @@ public class StandardConnectorRepository implements ConnectorRepository {
private void updateConnector(final ConnectorNode connector, final ConnectorState initialDesiredState, final ConnectorUpdateContext context) {
try {
// Perform whatever preparation is necessary for the update. Default implementation is to stop the connector.
+ logger.debug("Preparing {} for update", connector);
connector.prepareForUpdate();
// Wait for Connector State to become UPDATING
+ logger.debug("Waiting for {} to transition to UPDATING state", connector);
waitForState(connector, Set.of(ConnectorState.UPDATING), Set.of(ConnectorState.PREPARING_FOR_UPDATE));
// Apply the update to the connector.
+ logger.info("{} has now completed preparations for update; applying update now", connector);
connector.applyUpdate();
+ logger.info("{} has successfully applied update", connector);
// Now that the update has been applied, save the flow so that the updated configuration is persisted.
context.saveFlow();
- // Wait for Connector State to become UPDATED, or to revert to the initial desired state because, depending upon timing,
- // other nodes may have already seen the transition to UPDATED and moved the connector back to the initial desired state.
- final Set<ConnectorState> desirableStates = new HashSet<>();
- desirableStates.add(initialDesiredState);
- desirableStates.add(ConnectorState.UPDATED);
- if (initialDesiredState == ConnectorState.RUNNING) {
- desirableStates.add(ConnectorState.STARTING);
- } else if (initialDesiredState == ConnectorState.STOPPED) {
- desirableStates.add(ConnectorState.STOPPING);
- }
- waitForState(connector, desirableStates, Set.of(ConnectorState.UPDATING));
+ // Wait for all nodes to complete the update.
+ waitForState(connector, Set.of(ConnectorState.UPDATED), Set.of(ConnectorState.UPDATING));
+ logger.info("{} has successfully completed update on all nodes", connector);
// If the initial desired state was RUNNING, start the connector again. Otherwise, stop it.
// We don't simply leave it be as the prepareForUpdate / update may have changed the state of some components.
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardFlowContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardFlowContext.java
index 16ebbc3e39..70ff4e079d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardFlowContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardFlowContext.java
@@ -28,6 +28,7 @@ import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
import java.util.Collections;
+import java.util.HashMap;
public class StandardFlowContext implements FrameworkFlowContext {
private final ProcessGroup managedProcessGroup;
@@ -76,6 +77,10 @@ public class StandardFlowContext implements FrameworkFlowContext {
@Override
public void updateFlow(final VersionedExternalFlow versionedExternalFlow, final AssetManager assetManager) throws FlowUpdateException {
+ if (versionedExternalFlow.getParameterContexts() == null) {
+ versionedExternalFlow.setParameterContexts(new HashMap<>());
+ }
+
final String parameterContextName = managedProcessGroup.getParameterContext().getName();
updateParameterContext(versionedExternalFlow.getFlowContents(), parameterContextName);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index 4eba662d26..d306ac45aa 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -52,6 +52,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -509,6 +510,33 @@ public class TestStandardConnectorNode {
assertEquals(ConnectorState.STOPPED, connectorNode.getDesiredState());
}
+ @Test
+ @Timeout(value = 10, unit = TimeUnit.SECONDS)
+ public void testCancelDrainFlowFilesInterruptsConnector() throws Exception {
+ final BusyLoopDrainConnector busyLoopConnector = new BusyLoopDrainConnector();
+ final StandardConnectorNode connectorNode = createConnectorNode(busyLoopConnector);
+
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+
+ connectorNode.drainFlowFiles();
+
+ assertTrue(busyLoopConnector.awaitDrainStarted(2, TimeUnit.SECONDS));
+ assertEquals(ConnectorState.DRAINING, connectorNode.getCurrentState());
+
+ Thread.sleep(1000);
+
+ assertEquals(ConnectorState.DRAINING, connectorNode.getCurrentState());
+ assertFalse(busyLoopConnector.wasInterrupted());
+ assertFalse(busyLoopConnector.wasStopCalled());
+
+ connectorNode.cancelDrainFlowFiles();
+
+ assertTrue(busyLoopConnector.awaitDrainCompleted(2, TimeUnit.SECONDS));
+ assertTrue(busyLoopConnector.wasInterrupted());
+ assertTrue(busyLoopConnector.wasStopCalled());
+ assertEquals(ConnectorState.STOPPED, connectorNode.getCurrentState());
+ }
+
private StandardConnectorNode createConnectorNode() throws FlowUpdateException {
final SleepingConnector sleepingConnector = new SleepingConnector(Duration.ofMillis(1));
return createConnectorNode(sleepingConnector);
@@ -727,4 +755,94 @@ public class TestStandardConnectorNode {
}
}
+ /**
+ * Test connector that uses a busy loop for draining that never completes naturally,
+ * but can be interrupted via cancellation.
+ */
+ private static class BusyLoopDrainConnector extends AbstractConnector {
+ private volatile boolean interrupted = false;
+ private volatile boolean stopCalled = false;
+ private final AtomicReference<Thread> drainThreadRef = new AtomicReference<>();
+ private final CountDownLatch drainStartedLatch = new CountDownLatch(1);
+ private final CountDownLatch drainCompletedLatch = new CountDownLatch(1);
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ return null;
+ }
+
+ @Override
+ public void prepareForUpdate(final FlowContext workingContext, final FlowContext activeContext) {
+ }
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return List.of();
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingContext, final FlowContext activeContext) {
+ }
+
+ @Override
+ protected void onStepConfigured(final String stepName, final FlowContext workingContext) {
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final Map<String, String> overrides, final FlowContext flowContext) {
+ return List.of();
+ }
+
+ @Override
+ public void stop(final FlowContext activeContext) {
+ stopCalled = true;
+ }
+
+ @Override
+ public CompletableFuture<Void> drainFlowFiles(final FlowContext flowContext) {
+ final CompletableFuture<Void> future = new CompletableFuture<>();
+
+ final Thread drainThread = new Thread(() -> {
+ drainStartedLatch.countDown();
+ try {
+ while (!Thread.currentThread().isInterrupted()) {
+ // Busy loop that never completes naturally
+ }
+ } finally {
+ interrupted = Thread.currentThread().isInterrupted();
+ drainCompletedLatch.countDown();
+ }
+ });
+
+ drainThreadRef.set(drainThread);
+
+ future.whenComplete((result, throwable) -> {
+ final Thread thread = drainThreadRef.get();
+ if (thread != null) {
+ thread.interrupt();
+ }
+ });
+
+ drainThread.start();
+
+ return future;
+ }
+
+ public boolean awaitDrainStarted(final long timeout, final TimeUnit unit) throws InterruptedException {
+ return drainStartedLatch.await(timeout, unit);
+ }
+
+ public boolean awaitDrainCompleted(final long timeout, final TimeUnit unit) throws InterruptedException {
+ return drainCompletedLatch.await(timeout, unit);
+ }
+
+ public boolean wasInterrupted() {
+ return interrupted;
+ }
+
+ public boolean wasStopCalled() {
+ return stopCalled;
+ }
+ }
+
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 967c0e1f29..d270c0344f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -210,6 +210,14 @@ public interface NiFiServiceFacade {
ConnectorEntity scheduleConnector(Revision revision, String id, ScheduledState state);
+ void verifyDrainConnector(String id);
+
+ ConnectorEntity drainConnector(Revision revision, String id);
+
+ void verifyCancelConnectorDrain(String id);
+
+ ConnectorEntity cancelConnectorDrain(Revision revision, String id);
+
ConfigurationStepNamesEntity getConnectorConfigurationSteps(String id);
ConfigurationStepEntity getConnectorConfigurationStep(String id, String configurationStepName);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 1826a1efd4..bee52d0446 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -81,6 +81,7 @@ import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.components.connector.ConnectorUpdateContext;
import org.apache.nifi.components.connector.Secret;
import org.apache.nifi.components.connector.secrets.AuthorizableSecret;
@@ -3609,6 +3610,62 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
}
+ @Override
+ public void verifyDrainConnector(final String id) {
+ final ConnectorNode connector = connectorDAO.getConnector(id);
+ final ConnectorState currentState = connector.getCurrentState();
+ if (currentState != ConnectorState.STOPPED) {
+ throw new IllegalStateException("Cannot drain FlowFiles for Connector " + id + " because it is not currently stopped. Current state: " + currentState);
+ }
+ }
+
+ @Override
+ public ConnectorEntity drainConnector(final Revision revision, final String id) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ connectorDAO.drainFlowFiles(id);
+ controllerFacade.save();
+
+ final ConnectorNode node = connectorDAO.getConnector(id);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(node));
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
+ }
+
+ @Override
+ public void verifyCancelConnectorDrain(final String id) {
+ connectorDAO.verifyCancelDrainFlowFile(id);
+ }
+
+ @Override
+ public ConnectorEntity cancelConnectorDrain(final Revision revision, final String id) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ connectorDAO.cancelDrainFlowFiles(id);
+ controllerFacade.save();
+
+ final ConnectorNode node = connectorDAO.getConnector(id);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
+ final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(node));
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions);
+ }
+
@Override
public ConfigurationStepNamesEntity getConnectorConfigurationSteps(final String id) {
final ConnectorNode node = connectorDAO.getConnector(id);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
index cb5146afdb..f7cd6c50f0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
@@ -622,6 +622,155 @@ public class ConnectorResource extends ApplicationResource {
);
}
+ /**
+ * Initiates the draining of FlowFiles for the specified connector.
+ *
+ * @param id The id of the connector to drain.
+ * @param requestConnectorEntity A connectorEntity containing the revision.
+ * @return A connectorEntity.
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/drain")
+ @Operation(
+ summary = "Initiates draining of FlowFiles for a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "This will initiate draining of FlowFiles for a stopped connector. Draining allows the connector to process " +
+ "data that is currently in the flow but does not ingest any additional data. The connector must be in a STOPPED state " +
+ "before draining can begin. Once initiated, the connector will transition to a DRAINING state. Use the DELETE method " +
+ "on this endpoint to cancel an ongoing drain operation.",
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid} or /operation/connectors/{uuid}")
+ }
+ )
+ public Response initiateDrain(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "The connector entity with revision.",
+ required = true
+ ) final ConnectorEntity requestConnectorEntity) {
+
+ if (requestConnectorEntity == null || requestConnectorEntity.getRevision() == null) {
+ throw new IllegalArgumentException("Connector entity with revision must be specified.");
+ }
+
+ if (requestConnectorEntity.getId() != null && !id.equals(requestConnectorEntity.getId())) {
+ throw new IllegalArgumentException(String.format("The connector id (%s) in the request body does not equal the "
+ + "connector id of the requested resource (%s).", requestConnectorEntity.getId(), id));
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.POST, requestConnectorEntity);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(requestConnectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ final Revision requestRevision = getRevision(requestConnectorEntity, id);
+ return withWriteLock(
+ serviceFacade,
+ requestConnectorEntity,
+ requestRevision,
+ lookup -> {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final Authorizable connector = lookup.getConnector(id);
+ OperationAuthorizable.authorizeOperation(connector, authorizer, user);
+ },
+ () -> serviceFacade.verifyDrainConnector(id),
+ (revision, connectorEntity) -> {
+ final ConnectorEntity entity = serviceFacade.drainConnector(revision, id);
+ populateRemainingConnectorEntityContent(entity);
+
+ return generateOkResponse(entity).build();
+ }
+ );
+ }
+
+ /**
+ * Cancels the draining of FlowFiles for the specified connector.
+ *
+ * @param version The revision is used to verify the client is working with the latest version of the flow.
+ * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response.
+ * @param id The id of the connector to cancel draining for.
+ * @return A connectorEntity.
+ */
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/drain")
+ @Operation(
+ summary = "Cancels the draining of FlowFiles for a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid} or /operation/connectors/{uuid}")
+ }
+ )
+ public Response cancelDrain(
+ @Parameter(
+ description = "The revision is used to verify the client is working with the latest version of the flow."
+ )
+ @QueryParam(VERSION) final LongParameter version,
+ @Parameter(
+ description = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response."
+ )
+ @QueryParam(CLIENT_ID) final ClientIdParameter clientId,
+ @Parameter(
+ description = "Acknowledges that this node is disconnected to allow for mutable requests to proceed."
+ )
+ @QueryParam(DISCONNECTED_NODE_ACKNOWLEDGED) @DefaultValue("false") final Boolean disconnectedNodeAcknowledged,
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.DELETE);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(disconnectedNodeAcknowledged);
+ }
+
+ final ConnectorEntity requestConnectorEntity = new ConnectorEntity();
+ requestConnectorEntity.setId(id);
+
+ final Revision requestRevision = new Revision(version == null ? null : version.getLong(), clientId.getClientId(), id);
+ return withWriteLock(
+ serviceFacade,
+ requestConnectorEntity,
+ requestRevision,
+ lookup -> {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final Authorizable connector = lookup.getConnector(id);
+ OperationAuthorizable.authorizeOperation(connector, authorizer, user);
+ },
+ () -> serviceFacade.verifyCancelConnectorDrain(id),
+ (revision, connectorEntity) -> {
+ final ConnectorEntity entity = serviceFacade.cancelConnectorDrain(revision, id);
+ populateRemainingConnectorEntityContent(entity);
+
+ return generateOkResponse(entity).build();
+ }
+ );
+ }
+
/**
* Gets the configuration step names for the specified connector.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
index a987dde126..d094f18ca9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
@@ -45,6 +45,12 @@ public interface ConnectorDAO {
void stopConnector(String id);
+ void drainFlowFiles(String id);
+
+ void cancelDrainFlowFiles(String id);
+
+ void verifyCancelDrainFlowFile(String id);
+
void updateConnectorConfigurationStep(String id, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
void applyConnectorUpdate(String id, ConnectorUpdateContext updateContext);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
index e55e6ceee0..19b58dd207 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
@@ -127,6 +127,24 @@ public class StandardConnectorDAO implements ConnectorDAO {
getConnectorRepository().stopConnector(connector);
}
+ @Override
+ public void drainFlowFiles(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ connector.drainFlowFiles();
+ }
+
+ @Override
+ public void cancelDrainFlowFiles(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ connector.cancelDrainFlowFiles();
+ }
+
+ @Override
+ public void verifyCancelDrainFlowFile(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ connector.verifyCancelDrainFlowFiles();
+ }
+
@Override
public void updateConnectorConfigurationStep(final String id, final String configurationStepName, final ConfigurationStepConfigurationDTO configurationStepDto) {
final ConnectorNode connector = getConnector(id);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
index 17678723c7..60702b8b23 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
@@ -412,12 +412,72 @@ public class TestConnectorResource {
doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
assertThrows(AccessDeniedException.class, () ->
- connectorResource.getControllerServicesFromConnectorProcessGroup(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true));
+ connectorResource.getControllerServicesFromConnectorProcessGroup(CONNECTOR_ID, PROCESS_GROUP_ID, true, false, true));
verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
verify(serviceFacade, never()).getConnectorControllerServices(anyString(), anyString(), eq(true), eq(false), eq(true));
}
+ @Test
+ public void testInitiateDrain() {
+ final ConnectorEntity requestEntity = createConnectorEntity();
+ final ConnectorEntity responseEntity = createConnectorEntity();
+ responseEntity.getComponent().setState("DRAINING");
+
+ when(serviceFacade.drainConnector(any(Revision.class), eq(CONNECTOR_ID))).thenReturn(responseEntity);
+
+ try (Response response = connectorResource.initiateDrain(CONNECTOR_ID, requestEntity)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(responseEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).verifyDrainConnector(CONNECTOR_ID);
+ verify(serviceFacade).drainConnector(any(Revision.class), eq(CONNECTOR_ID));
+ }
+
+ @Test
+ public void testInitiateDrainNotAuthorized() {
+ final ConnectorEntity requestEntity = createConnectorEntity();
+
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () ->
+ connectorResource.initiateDrain(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).verifyDrainConnector(anyString());
+ verify(serviceFacade, never()).drainConnector(any(Revision.class), anyString());
+ }
+
+ @Test
+ public void testInitiateDrainWithNullEntity() {
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.initiateDrain(CONNECTOR_ID, null));
+
+ verify(serviceFacade, never()).drainConnector(any(Revision.class), anyString());
+ }
+
+ @Test
+ public void testInitiateDrainWithNullRevision() {
+ final ConnectorEntity requestEntity = createConnectorEntity();
+ requestEntity.setRevision(null);
+
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.initiateDrain(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).drainConnector(any(Revision.class), anyString());
+ }
+
+ @Test
+ public void testInitiateDrainWithMismatchedId() {
+ final ConnectorEntity requestEntity = createConnectorEntity();
+ requestEntity.setId("different-id");
+
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.initiateDrain(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).drainConnector(any(Revision.class), anyString());
+ }
+
private ConnectorEntity createConnectorEntity() {
final ConnectorEntity entity = new ConnectorEntity();
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
index 920c61480a..987b7e812d 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
@@ -50,7 +50,8 @@ public class DataQueuingConnector extends AbstractConnector {
bundle.setVersion("2.8.0-SNAPSHOT");
final VersionedProcessor generate = createVersionedProcessor("gen-1", "1234", "GenerateFlowFile",
- "org.apache.nifi.processors.tests.system.GenerateFlowFile", bundle, Map.of("File Size", "1 KB"), ScheduledState.RUNNING);
+ "org.apache.nifi.processors.tests.system.GenerateFlowFile", bundle, Map.of("File Size", "1 KB"), ScheduledState.ENABLED);
+ generate.setSchedulingPeriod("100 millis");
final VersionedProcessor terminate = createVersionedProcessor("term-1", "1234", "TerminateFlowFile",
"org.apache.nifi.processors.tests.system.TerminateFlowFile", bundle, Collections.emptyMap(), ScheduledState.DISABLED);
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/GatedDataQueuingConnector.java
similarity index 64%
copy from nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
copy to nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/GatedDataQueuingConnector.java
index 920c61480a..857e77234f 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/DataQueuingConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/GatedDataQueuingConnector.java
@@ -20,6 +20,10 @@ package org.apache.nifi.connectors.tests.system;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.connector.AbstractConnector;
import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.PropertyType;
import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.flow.Bundle;
import org.apache.nifi.flow.ConnectableComponent;
@@ -30,16 +34,47 @@ import org.apache.nifi.flow.VersionedConnection;
import org.apache.nifi.flow.VersionedExternalFlow;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.flow.VersionedProcessor;
+import org.apache.nifi.processor.util.StandardValidators;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-public class DataQueuingConnector extends AbstractConnector {
+/**
+ * A connector that generates FlowFiles and allows controlling when the TerminateFlowFile processor
+ * will process them via a gate file. If the gate file does not exist, FlowFiles will queue up.
+ * When the gate file exists, the TerminateFlowFile processor will process the queued FlowFiles.
+ * <p>
+ * The connector uses a configuration step to specify the gate file path. The test must configure
+ * the connector with a gate file path and apply the update before starting the connector.
+ */
+public class GatedDataQueuingConnector extends AbstractConnector {
+
+ private static final String TERMINATE_PROCESSOR_ID = "term-1";
+
+ static final ConnectorPropertyDescriptor GATE_FILE_PATH = new ConnectorPropertyDescriptor.Builder()
+ .name("Gate File Path")
+ .description("The path to the gate file. When this file exists, the TerminateFlowFile processor " +
+ "will process FlowFiles. When it does not exist, FlowFiles will queue up.")
+ .required(true)
+ .type(PropertyType.STRING)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ private static final ConnectorPropertyGroup PROPERTY_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Gate Configuration")
+ .addProperty(GATE_FILE_PATH)
+ .build();
+
+ private static final ConfigurationStep CONFIG_STEP = new ConfigurationStep.Builder()
+ .name("Gate Configuration")
+ .propertyGroups(List.of(PROPERTY_GROUP))
+ .build();
+
@Override
protected void onStepConfigured(final String stepName, final FlowContext workingContext) {
-
}
@Override
@@ -47,13 +82,16 @@ public class DataQueuingConnector extends AbstractConnector {
final Bundle bundle = new Bundle();
bundle.setGroup("org.apache.nifi");
bundle.setArtifact("nifi-system-test-extensions-nar");
- bundle.setVersion("2.8.0-SNAPSHOT");
+ bundle.setVersion("2.7.0-SNAPSHOT");
final VersionedProcessor generate = createVersionedProcessor("gen-1", "1234", "GenerateFlowFile",
- "org.apache.nifi.processors.tests.system.GenerateFlowFile", bundle, Map.of("File Size", "1 KB"), ScheduledState.RUNNING);
+ "org.apache.nifi.processors.tests.system.GenerateFlowFile", bundle,
+ Map.of("File Size", "1 KB"), ScheduledState.ENABLED);
+ generate.setSchedulingPeriod("100 millis");
- final VersionedProcessor terminate = createVersionedProcessor("term-1", "1234", "TerminateFlowFile",
- "org.apache.nifi.processors.tests.system.TerminateFlowFile", bundle, Collections.emptyMap(), ScheduledState.DISABLED);
+ final VersionedProcessor terminate = createVersionedProcessor(TERMINATE_PROCESSOR_ID, "1234", "TerminateFlowFile",
+ "org.apache.nifi.processors.tests.system.TerminateFlowFile", bundle,
+ Collections.emptyMap(), ScheduledState.ENABLED);
final ConnectableComponent source = new ConnectableComponent();
source.setId(generate.getIdentifier());
@@ -80,7 +118,7 @@ public class DataQueuingConnector extends AbstractConnector {
connection.setzIndex(1L);
final VersionedProcessGroup rootGroup = new VersionedProcessGroup();
- rootGroup.setName("Data Queuing Connector");
+ rootGroup.setName("Gated Data Queuing Connector");
rootGroup.setIdentifier("1234");
rootGroup.setProcessors(Set.of(generate, terminate));
rootGroup.setConnections(Set.of(connection));
@@ -98,11 +136,28 @@ public class DataQueuingConnector extends AbstractConnector {
@Override
public List<ConfigurationStep> getConfigurationSteps() {
- return List.of();
+ return List.of(CONFIG_STEP);
}
@Override
- public void applyUpdate(final FlowContext workingFlowContext, final FlowContext activeFlowContext) {
+ public void applyUpdate(final FlowContext workingFlowContext, final FlowContext activeFlowContext) throws FlowUpdateException {
+ final String gateFilePath = workingFlowContext.getConfigurationContext().getProperty(CONFIG_STEP, GATE_FILE_PATH).getValue();
+ if (gateFilePath == null) {
+ return;
+ }
+
+ final VersionedExternalFlow flow = getInitialFlow();
+ final VersionedProcessGroup rootGroup = flow.getFlowContents();
+
+ for (final VersionedProcessor processor : rootGroup.getProcessors()) {
+ if (TERMINATE_PROCESSOR_ID.equals(processor.getIdentifier())) {
+ final Map<String, String> properties = new HashMap<>(processor.getProperties());
+ properties.put("Gate File", gateFilePath);
+ processor.setProperties(properties);
+ }
+ }
+
+ getInitializationContext().updateFlow(activeFlowContext, flow);
}
private VersionedProcessor createVersionedProcessor(final String identifier, final String groupIdentifier, final String name,
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/TerminateFlowFile.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/TerminateFlowFile.java
index 734766fa37..9ab6fd71b1 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/TerminateFlowFile.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/TerminateFlowFile.java
@@ -16,15 +16,43 @@
*/
package org.apache.nifi.processors.tests.system;
+import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.io.File;
+import java.util.List;
public class TerminateFlowFile extends AbstractProcessor {
+
+ public static final PropertyDescriptor GATE_FILE = new PropertyDescriptor.Builder()
+ .name("Gate File")
+ .description("An optional file path. If specified, the processor will only process FlowFiles when this file exists. " +
+ "If the file does not exist, the processor will yield and return without processing any data.")
+ .required(false)
+ .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
+ .build();
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return List.of(GATE_FILE);
+ }
+
@Override
public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
+ final String gateFilePath = context.getProperty(GATE_FILE).getValue();
+ if (gateFilePath != null) {
+ final File gateFile = new File(gateFilePath);
+ if (!gateFile.exists()) {
+ context.yield();
+ return;
+ }
+ }
+
FlowFile flowFile = session.get();
if (flowFile == null) {
return;
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
index 34fe7c90ea..3885e4c079 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -16,5 +16,6 @@
org.apache.nifi.connectors.tests.system.NopConnector
org.apache.nifi.connectors.tests.system.AssetConnector
org.apache.nifi.connectors.tests.system.DataQueuingConnector
+org.apache.nifi.connectors.tests.system.GatedDataQueuingConnector
org.apache.nifi.connectors.tests.system.NestedProcessGroupConnector
org.apache.nifi.connectors.tests.system.CalculateConnector
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
index 51272e8b8e..1a3b91c366 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
@@ -406,6 +406,16 @@ public class NiFiClientUtil {
}
}
+ public void startConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException, InterruptedException {
+ startConnector(connectorEntity.getId());
+ }
+
+ public void startConnector(final String connectorId) throws NiFiClientException, IOException, InterruptedException {
+ final ConnectorEntity entity = getConnectorClient().getConnector(connectorId);
+ getConnectorClient().startConnector(entity);
+ waitForConnectorState(connectorId, ConnectorState.RUNNING);
+ }
+
public void stopConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException, InterruptedException {
stopConnector(connectorEntity.getId());
}
@@ -437,6 +447,20 @@ public class NiFiClientUtil {
}
}
+ public ConnectorEntity drainConnector(final String connectorId) throws NiFiClientException, IOException {
+ final ConnectorEntity entity = getConnectorClient().getConnector(connectorId);
+ return getConnectorClient().drainConnector(entity);
+ }
+
+ public ConnectorEntity cancelDrain(final String connectorId) throws NiFiClientException, IOException {
+ final ConnectorEntity entity = getConnectorClient().getConnector(connectorId);
+ return getConnectorClient().cancelDrain(entity);
+ }
+
+ public void waitForConnectorDraining(final String connectorId) throws NiFiClientException, IOException, InterruptedException {
+ waitForConnectorState(connectorId, ConnectorState.DRAINING);
+ }
+
public ParameterProviderEntity createParameterProvider(final String simpleTypeName) throws NiFiClientException, IOException {
return createParameterProvider(NiFiSystemIT.TEST_PARAM_PROVIDERS_PACKAGE + "." + simpleTypeName, NiFiSystemIT.NIFI_GROUP_ID, NiFiSystemIT.TEST_EXTENSIONS_ARTIFACT_ID, nifiVersion);
}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
index b73814250f..93614725e7 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
@@ -468,6 +468,29 @@ public abstract class NiFiSystemIT implements NiFiInstanceProvider {
logger.info("Queue Count for Connection {} is now {}", connectionId, queueSize);
}
+ protected void waitForConnectorMinQueueCount(final String connectorId, final int minQueueSize) throws InterruptedException {
+ logger.info("Waiting for Queue Count of at least {} in Connector {}", minQueueSize, connectorId);
+
+ waitFor(() -> {
+ try {
+ final ProcessGroupStatusEntity statusEntity = getNifiClient().getConnectorClient().getStatus(connectorId, true);
+ final int currentSize = statusEntity.getProcessGroupStatus().getAggregateSnapshot().getFlowFilesQueued();
+ logEverySecond("Current Queue Size for Connector {} = {}, Waiting for at least {}", connectorId, currentSize, minQueueSize);
+ return currentSize >= minQueueSize;
+ } catch (final Exception e) {
+ logger.error("Failed to get connector queue count", e);
+ return false;
+ }
+ });
+
+ logger.info("Queue Count for Connector {} is now at least {}", connectorId, minQueueSize);
+ }
+
+ protected int getConnectorQueuedFlowFileCount(final String connectorId) throws NiFiClientException, IOException {
+ final ProcessGroupStatusEntity statusEntity = getNifiClient().getConnectorClient().getStatus(connectorId, true);
+ return statusEntity.getProcessGroupStatus().getAggregateSnapshot().getFlowFilesQueued();
+ }
+
private void waitForQueueCountToMatch(final String connectionId, final Predicate<Integer> test, final String queueSizeDescription) throws InterruptedException {
waitFor(() -> {
final ConnectionStatusEntity statusEntity = getConnectionStatus(connectionId);
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorDrainIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorDrainIT.java
new file mode 100644
index 0000000000..9ee3fbd09b
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ClusteredConnectorDrainIT.java
@@ -0,0 +1,228 @@
+/*
+ * 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.tests.system.connectors;
+
+import org.apache.nifi.components.connector.ConnectorState;
+import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Cluster-specific tests for connector draining functionality.
+ * These tests verify that draining works correctly when nodes complete draining at different times.
+ */
+public class ClusteredConnectorDrainIT extends NiFiSystemIT {
+
+ private static final Logger logger = LoggerFactory.getLogger(ClusteredConnectorDrainIT.class);
+
+ @Override
+ public NiFiInstanceFactory getInstanceFactory() {
+ return createTwoNodeInstanceFactory();
+ }
+
+ /**
+ * Tests that when draining in a cluster:
+ * 1. Create gate file on Node 1 only - Node 1 finishes draining (STOPPED), Node 2 still DRAINING
+ * 2. Aggregate state should be DRAINING (since at least one node is still draining)
+ * 3. Create gate file on Node 2 - both nodes finish draining
+ * 4. Aggregate state should be STOPPED
+ */
+ @Test
+ public void testDrainWithNodeCompletingAtDifferentTimes() throws NiFiClientException, IOException, InterruptedException {
+ final File node1InstanceDir = getNiFiInstance().getNodeInstance(1).getInstanceDirectory();
+ final File node2InstanceDir = getNiFiInstance().getNodeInstance(2).getInstanceDirectory();
+ final File node1GateFile = new File(node1InstanceDir, "gate-file.txt");
+ final File node2GateFile = new File(node2InstanceDir, "gate-file.txt");
+ node1GateFile.deleteOnExit();
+ node2GateFile.deleteOnExit();
+ deleteIfExists(node1GateFile);
+ deleteIfExists(node2GateFile);
+
+ logger.info("Creating GatedDataQueuingConnector");
+ final ConnectorEntity connector = getClientUtil().createConnector("GatedDataQueuingConnector");
+ assertNotNull(connector);
+ final String connectorId = connector.getId();
+
+ final String gateFilePath = "./gate-file.txt";
+ logger.info("Configuring connector {} with gate file path: {}", connectorId, gateFilePath);
+ getClientUtil().configureConnector(connector, "Gate Configuration", Map.of("Gate File Path", gateFilePath));
+ getClientUtil().applyConnectorUpdate(connector);
+
+ logger.info("Starting connector {}", connectorId);
+ getClientUtil().startConnector(connectorId);
+
+ logger.info("Waiting for at least 5 FlowFiles to queue");
+ waitForConnectorMinQueueCount(connectorId, 5);
+
+ logger.info("Stopping connector {}", connectorId);
+ getClientUtil().stopConnector(connectorId);
+ getClientUtil().waitForConnectorStopped(connectorId);
+
+ final int queuedCountBeforeDrain = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Queued FlowFile count before drain: {}", queuedCountBeforeDrain);
+ assertTrue(queuedCountBeforeDrain > 0);
+
+ logger.info("Initiating drain for connector {}", connectorId);
+ getClientUtil().drainConnector(connectorId);
+
+ logger.info("Waiting for aggregate connector state to be DRAINING");
+ getClientUtil().waitForConnectorDraining(connectorId);
+
+ logger.info("Creating gate file for Node 1 only: {}", node1GateFile.getAbsolutePath());
+ assertTrue(node1GateFile.createNewFile());
+
+ logger.info("Waiting for Node 1 to finish draining and become STOPPED");
+ waitForNodeConnectorState(1, connectorId, ConnectorState.STOPPED);
+ waitForNodeConnectorState(2, connectorId, ConnectorState.DRAINING);
+
+ logger.info("Verifying aggregate state is still DRAINING (Node 2 still draining)");
+ final ConnectorEntity aggregateConnector = getNifiClient().getConnectorClient().getConnector(connectorId);
+ assertEquals(ConnectorState.DRAINING.name(), aggregateConnector.getComponent().getState());
+
+ logger.info("Creating gate file for Node 2: {}", node2GateFile.getAbsolutePath());
+ assertTrue(node2GateFile.createNewFile());
+
+ logger.info("Waiting for aggregate state to become STOPPED");
+ waitFor(() -> {
+ try {
+ final ConnectorEntity entity = getNifiClient().getConnectorClient().getConnector(connectorId);
+ return ConnectorState.STOPPED.name().equals(entity.getComponent().getState());
+ } catch (final Exception e) {
+ return false;
+ }
+ });
+
+ final ConnectorEntity finalConnector = getNifiClient().getConnectorClient().getConnector(connectorId);
+ logger.info("Final aggregate connector state: {}", finalConnector.getComponent().getState());
+ assertEquals(ConnectorState.STOPPED.name(), finalConnector.getComponent().getState());
+
+ final int finalQueuedCount = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Final queued FlowFile count: {}", finalQueuedCount);
+ assertEquals(0, finalQueuedCount);
+
+ logger.info("testDrainWithNodeCompletingAtDifferentTimes completed successfully");
+ }
+
+ /**
+ * Tests that when canceling drain in a cluster where one node has already finished:
+ * 1. Create gate file on Node 1 only - Node 1 finishes draining (STOPPED), Node 2 still DRAINING
+ * 2. Aggregate state should be DRAINING
+ * 3. Cancel drain - Node 2 should stop draining
+ * 4. Aggregate state should be STOPPED
+ * 5. Data should still be queued (from Node 2)
+ */
+ @Test
+ public void testCancelDrainWithOneNodeAlreadyComplete() throws NiFiClientException, IOException, InterruptedException {
+ final File node1InstanceDir = getNiFiInstance().getNodeInstance(1).getInstanceDirectory();
+ final File node2InstanceDir = getNiFiInstance().getNodeInstance(2).getInstanceDirectory();
+ final File node1GateFile = new File(node1InstanceDir, "gate-file-cancel.txt");
+ final File node2GateFile = new File(node2InstanceDir, "gate-file-cancel.txt");
+ node1GateFile.deleteOnExit();
+ node2GateFile.deleteOnExit();
+ deleteIfExists(node1GateFile);
+ deleteIfExists(node2GateFile);
+
+ logger.info("Creating GatedDataQueuingConnector");
+ final ConnectorEntity connector = getClientUtil().createConnector("GatedDataQueuingConnector");
+ assertNotNull(connector);
+ final String connectorId = connector.getId();
+
+ final String gateFilePath = "./gate-file-cancel.txt";
+ logger.info("Configuring connector {} with gate file path: {}", connectorId, gateFilePath);
+ getClientUtil().configureConnector(connector, "Gate Configuration", Map.of("Gate File Path", gateFilePath));
+ getClientUtil().applyConnectorUpdate(connector);
+
+ logger.info("Starting connector {}", connectorId);
+ getClientUtil().startConnector(connectorId);
+
+ logger.info("Waiting for at least 5 FlowFiles to queue");
+ waitForConnectorMinQueueCount(connectorId, 5);
+
+ logger.info("Stopping connector {}", connectorId);
+ getClientUtil().stopConnector(connectorId);
+ getClientUtil().waitForConnectorStopped(connectorId);
+
+ final int queuedCountBeforeDrain = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Queued FlowFile count before drain: {}", queuedCountBeforeDrain);
+ assertTrue(queuedCountBeforeDrain > 0);
+
+ logger.info("Initiating drain for connector {}", connectorId);
+ getClientUtil().drainConnector(connectorId);
+
+ logger.info("Waiting for aggregate connector state to be DRAINING");
+ getClientUtil().waitForConnectorDraining(connectorId);
+
+ logger.info("Creating gate file for Node 1 only: {}", node1GateFile.getAbsolutePath());
+ assertTrue(node1GateFile.createNewFile());
+
+ logger.info("Waiting for Node 1 to finish draining and become STOPPED");
+ waitForNodeConnectorState(1, connectorId, ConnectorState.STOPPED);
+
+ logger.info("Verifying aggregate state is still DRAINING (Node 2 still draining)");
+ final ConnectorEntity aggregateBeforeCancel = getNifiClient().getConnectorClient().getConnector(connectorId);
+ assertEquals(ConnectorState.DRAINING.name(), aggregateBeforeCancel.getComponent().getState());
+
+ logger.info("Canceling drain for connector {}", connectorId);
+ getClientUtil().cancelDrain(connectorId);
+
+ logger.info("Waiting for aggregate state to become STOPPED after cancel");
+ getClientUtil().waitForConnectorStopped(connectorId);
+
+ final ConnectorEntity finalConnector = getNifiClient().getConnectorClient().getConnector(connectorId);
+ logger.info("Final aggregate connector state: {}", finalConnector.getComponent().getState());
+ assertEquals(ConnectorState.STOPPED.name(), finalConnector.getComponent().getState());
+
+ final int finalQueuedCount = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Final queued FlowFile count after cancel (should still have data from Node 2): {}", finalQueuedCount);
+ assertTrue(finalQueuedCount > 0);
+
+ logger.info("testCancelDrainWithOneNodeAlreadyComplete completed successfully");
+ }
+
+ private void waitForNodeConnectorState(final int nodeIndex, final String connectorId, final ConnectorState expectedState) throws InterruptedException {
+ logger.info("Waiting for Node {} connector {} to reach state {}", nodeIndex, connectorId, expectedState);
+ waitFor(() -> {
+ try {
+ switchClientToNode(nodeIndex);
+ final ConnectorEntity entity = getNifiClient().getConnectorClient(DO_NOT_REPLICATE).getConnector(connectorId);
+ return expectedState.name().equals(entity.getComponent().getState());
+ } catch (final Exception e) {
+ return false;
+ }
+ });
+ logger.info("Node {} connector {} reached state {}", nodeIndex, connectorId, expectedState);
+ }
+
+ private void deleteIfExists(final File file) {
+ if (file.exists()) {
+ assertTrue(file.delete());
+ }
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorDrainIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorDrainIT.java
new file mode 100644
index 0000000000..eef6c4bf97
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/connectors/ConnectorDrainIT.java
@@ -0,0 +1,178 @@
+/*
+ * 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.tests.system.connectors;
+
+import org.apache.nifi.components.connector.ConnectorState;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ConnectorDrainIT extends NiFiSystemIT {
+
+ private static final Logger logger = LoggerFactory.getLogger(ConnectorDrainIT.class);
+
+ @Test
+ public void testDrainFlowFiles() throws NiFiClientException, IOException, InterruptedException {
+ final File gateFile = new File(getNiFiInstance().getInstanceDirectory(), "gate-file.txt");
+ gateFile.deleteOnExit();
+
+ if (gateFile.exists()) {
+ assertTrue(gateFile.delete());
+ }
+
+ logger.info("Creating GatedDataQueuingConnector");
+ final ConnectorEntity connector = getClientUtil().createConnector("GatedDataQueuingConnector");
+ assertNotNull(connector);
+ final String connectorId = connector.getId();
+
+ logger.info("Configuring connector {} with gate file path: {}", connectorId, gateFile.getAbsolutePath());
+ getClientUtil().configureConnector(connector, "Gate Configuration", Map.of("Gate File Path", gateFile.getAbsolutePath()));
+ getClientUtil().applyConnectorUpdate(connector);
+
+ logger.info("Starting connector {}", connectorId);
+ getClientUtil().startConnector(connectorId);
+
+ logger.info("Waiting for at least 5 FlowFiles to queue");
+ waitForConnectorMinQueueCount(connectorId, 5);
+
+ logger.info("Stopping connector {}", connectorId);
+ getClientUtil().stopConnector(connectorId);
+ getClientUtil().waitForConnectorStopped(connectorId);
+
+ final int queuedCountBeforeDrain = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Queued FlowFile count before drain: {}", queuedCountBeforeDrain);
+ assertTrue(queuedCountBeforeDrain > 0);
+
+ logger.info("Initiating drain for connector {}", connectorId);
+ getClientUtil().drainConnector(connectorId);
+
+ logger.info("Waiting for connector to enter DRAINING state");
+ getClientUtil().waitForConnectorDraining(connectorId);
+
+ logger.info("Sleeping for 2 seconds to verify connector remains in DRAINING state");
+ Thread.sleep(2000L);
+
+ ConnectorEntity drainingConnector = getNifiClient().getConnectorClient().getConnector(connectorId);
+ logger.info("Connector state after 2 seconds: {}", drainingConnector.getComponent().getState());
+ assertEquals(ConnectorState.DRAINING.name(), drainingConnector.getComponent().getState());
+
+ final int queuedWhileDraining = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Queued FlowFile count while draining (gate file absent): {}", queuedWhileDraining);
+ assertTrue(queuedWhileDraining > 0);
+
+ logger.info("Creating gate file to allow draining to complete: {}", gateFile.getAbsolutePath());
+ assertTrue(gateFile.createNewFile());
+
+ logger.info("Waiting for connector to complete draining and return to STOPPED state");
+ waitFor(() -> {
+ try {
+ final ConnectorEntity entity = getNifiClient().getConnectorClient().getConnector(connectorId);
+ return ConnectorState.STOPPED.name().equals(entity.getComponent().getState());
+ } catch (final Exception e) {
+ return false;
+ }
+ });
+
+ final ConnectorEntity finalConnector = getNifiClient().getConnectorClient().getConnector(connectorId);
+ logger.info("Final connector state: {}", finalConnector.getComponent().getState());
+ assertEquals(ConnectorState.STOPPED.name(), finalConnector.getComponent().getState());
+
+ final int finalQueuedCount = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Final queued FlowFile count: {}", finalQueuedCount);
+ assertEquals(0, finalQueuedCount);
+
+ logger.info("testDrainFlowFiles completed successfully");
+ }
+
+ @Test
+ public void testCancelDrainFlowFiles() throws NiFiClientException, IOException, InterruptedException {
+ final File gateFile = new File(getNiFiInstance().getInstanceDirectory(), "gate-file-cancel.txt");
+ gateFile.deleteOnExit();
+
+ if (gateFile.exists()) {
+ assertTrue(gateFile.delete());
+ }
+
+ logger.info("Creating GatedDataQueuingConnector");
+ final ConnectorEntity connector = getClientUtil().createConnector("GatedDataQueuingConnector");
+ assertNotNull(connector);
+ final String connectorId = connector.getId();
+
+ logger.info("Configuring connector {} with gate file path: {}", connectorId, gateFile.getAbsolutePath());
+ getClientUtil().configureConnector(connector, "Gate Configuration", Map.of("Gate File Path", gateFile.getAbsolutePath()));
+ getClientUtil().applyConnectorUpdate(connector);
+
+ logger.info("Starting connector {}", connectorId);
+ getClientUtil().startConnector(connectorId);
+
+ logger.info("Waiting for at least 5 FlowFiles to queue");
+ waitForConnectorMinQueueCount(connectorId, 5);
+
+ logger.info("Stopping connector {}", connectorId);
+ getClientUtil().stopConnector(connectorId);
+ getClientUtil().waitForConnectorStopped(connectorId);
+
+ final int queuedCountBeforeDrain = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Queued FlowFile count before drain: {}", queuedCountBeforeDrain);
+ assertTrue(queuedCountBeforeDrain > 0);
+
+ logger.info("Initiating drain for connector {}", connectorId);
+ getClientUtil().drainConnector(connectorId);
+
+ logger.info("Waiting for connector to enter DRAINING state");
+ getClientUtil().waitForConnectorDraining(connectorId);
+
+ logger.info("Sleeping for 2 seconds to verify connector remains in DRAINING state");
+ Thread.sleep(2000L);
+
+ ConnectorEntity drainingConnector = getNifiClient().getConnectorClient().getConnector(connectorId);
+ logger.info("Connector state after 2 seconds: {}", drainingConnector.getComponent().getState());
+ assertEquals(ConnectorState.DRAINING.name(), drainingConnector.getComponent().getState());
+
+ final int queuedCountWhileDraining = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Queued FlowFile count while draining: {}", queuedCountWhileDraining);
+ assertTrue(queuedCountWhileDraining > 0);
+
+ logger.info("Canceling drain for connector {}", connectorId);
+ getClientUtil().cancelDrain(connectorId);
+
+ logger.info("Waiting for connector to return to STOPPED state after cancel");
+ getClientUtil().waitForConnectorStopped(connectorId);
+
+ final ConnectorEntity finalConnector = getNifiClient().getConnectorClient().getConnector(connectorId);
+ logger.info("Final connector state: {}", finalConnector.getComponent().getState());
+ assertEquals(ConnectorState.STOPPED.name(), finalConnector.getComponent().getState());
+
+ final int queuedCountAfterCancel = getConnectorQueuedFlowFileCount(connectorId);
+ logger.info("Queued FlowFile count after cancel (should still have data): {}", queuedCountAfterCancel);
+ assertTrue(queuedCountAfterCancel > 0);
+
+ logger.info("testCancelDrainFlowFiles completed successfully");
+ }
+}
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
index 1e95222225..76dfefaeaa 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/ConnectorClient.java
@@ -131,6 +131,50 @@ public interface ConnectorClient {
*/
ConnectorEntity stopConnector(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+ /**
+ * Initiates draining of FlowFiles for a connector.
+ *
+ * @param connectorId the connector ID
+ * @param clientId the client ID
+ * @param version the revision version
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity drainConnector(String connectorId, String clientId, long version) throws NiFiClientException, IOException;
+
+ /**
+ * Initiates draining of FlowFiles for a connector using the information from the entity.
+ *
+ * @param connectorEntity the connector entity to drain
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity drainConnector(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+
+ /**
+ * Cancels an ongoing drain operation for a connector.
+ *
+ * @param connectorId the connector ID
+ * @param clientId the client ID
+ * @param version the revision version
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity cancelDrain(String connectorId, String clientId, long version) throws NiFiClientException, IOException;
+
+ /**
+ * Cancels an ongoing drain operation for a connector using the information from the entity.
+ *
+ * @param connectorEntity the connector entity to cancel draining for
+ * @return the updated connector entity
+ * @throws NiFiClientException if an error occurs during the request
+ * @throws IOException if an I/O error occurs
+ */
+ ConnectorEntity cancelDrain(ConnectorEntity connectorEntity) throws NiFiClientException, IOException;
+
/**
* Gets the configuration step names for a connector.
*
diff --git a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
index b5ad5878d5..4f9b8d8aaa 100644
--- a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
+++ b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/JerseyConnectorClient.java
@@ -164,6 +164,75 @@ public class JerseyConnectorClient extends AbstractJerseyClient implements Conne
connectorEntity.getRevision().getVersion(), connectorEntity.isDisconnectedNodeAcknowledged());
}
+ @Override
+ public ConnectorEntity drainConnector(final String connectorId, final String clientId, final long version) throws NiFiClientException, IOException {
+ return drainConnector(connectorId, clientId, version, false);
+ }
+
+ @Override
+ public ConnectorEntity drainConnector(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException {
+ return drainConnector(connectorEntity.getId(), connectorEntity.getRevision().getClientId(),
+ connectorEntity.getRevision().getVersion(), connectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ private ConnectorEntity drainConnector(final String connectorId, final String clientId, final long version,
+ final Boolean disconnectedNodeAcknowledged) throws NiFiClientException, IOException {
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector ID cannot be null or blank");
+ }
+
+ return executeAction("Error initiating connector drain", () -> {
+ final WebTarget target = connectorTarget
+ .path("/drain")
+ .resolveTemplate("id", connectorId);
+
+ final ConnectorEntity requestEntity = new ConnectorEntity();
+ requestEntity.setId(connectorId);
+ requestEntity.setDisconnectedNodeAcknowledged(disconnectedNodeAcknowledged);
+
+ final RevisionDTO revisionDto = new RevisionDTO();
+ revisionDto.setClientId(clientId);
+ revisionDto.setVersion(version);
+ requestEntity.setRevision(revisionDto);
+
+ return getRequestBuilder(target).post(
+ Entity.entity(requestEntity, MediaType.APPLICATION_JSON_TYPE),
+ ConnectorEntity.class);
+ });
+ }
+
+ @Override
+ public ConnectorEntity cancelDrain(final String connectorId, final String clientId, final long version) throws NiFiClientException, IOException {
+ return cancelDrain(connectorId, clientId, version, false);
+ }
+
+ @Override
+ public ConnectorEntity cancelDrain(final ConnectorEntity connectorEntity) throws NiFiClientException, IOException {
+ return cancelDrain(connectorEntity.getId(), connectorEntity.getRevision().getClientId(),
+ connectorEntity.getRevision().getVersion(), connectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ private ConnectorEntity cancelDrain(final String connectorId, final String clientId, final long version,
+ final Boolean disconnectedNodeAcknowledged) throws NiFiClientException, IOException {
+ if (StringUtils.isBlank(connectorId)) {
+ throw new IllegalArgumentException("Connector ID cannot be null or blank");
+ }
+
+ return executeAction("Error canceling connector drain", () -> {
+ WebTarget target = connectorTarget
+ .path("/drain")
+ .queryParam("version", version)
+ .queryParam("clientId", clientId)
+ .resolveTemplate("id", connectorId);
+
+ if (disconnectedNodeAcknowledged == Boolean.TRUE) {
+ target = target.queryParam("disconnectedNodeAcknowledged", "true");
+ }
+
+ return getRequestBuilder(target).delete(ConnectorEntity.class);
+ });
+ }
+
private ConnectorEntity updateConnectorRunStatus(final String connectorId, final String desiredState, final String clientId,
final long version, final Boolean disconnectedNodeAcknowledged) throws NiFiClientException, IOException {
if (StringUtils.isBlank(connectorId)) {
(nifi) 39/40: NIFI-15258: Addressed checkstyle violations
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit afaa1be9b2f7bd1d4ce9c5aa2b5c2decb053449b
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Wed Jan 21 17:29:11 2026 -0500
NIFI-15258: Addressed checkstyle violations
---
.../org/apache/nifi/connectors/kafkas3/KafkaTopicsStep.java | 2 +-
.../org/apache/nifi/kafka/service/consumer/RecordIterable.java | 2 +-
.../nifi/web/api/dto/ConfigurationStepConfigurationDTO.java | 2 +-
.../apache/nifi/web/api/dto/PropertyGroupConfigurationDTO.java | 2 +-
.../org/apache/nifi/web/api/entity/ConnectorTypesEntity.java | 3 ++-
.../apache/nifi/authorization/resource/ResourceFactory.java | 2 +-
.../cluster/coordination/http/StandardHttpResponseMapper.java | 4 ++--
.../http/endpoints/ConnectorPropertyGroupEndpointMerger.java | 2 +-
.../endpoints/ConnectorPropertyGroupNamesEndpointMerger.java | 2 +-
.../http/replication/ThreadPoolRequestReplicator.java | 2 +-
.../connector/secrets/ParameterProviderSecretsManager.java | 3 +--
.../connector/ConnectorRepositoryInitializationContext.java | 2 +-
.../connector/StandardConnectorRepoInitializationContext.java | 2 +-
.../nifi/components/connector/StandardConnectorRepository.java | 4 ++--
.../nifi/controller/serialization/VersionedDataflowMapper.java | 6 ++----
.../service/TestStandardControllerServiceProvider.java | 1 -
.../apache/nifi/nar/StandardExtensionDiscoveringManager.java | 2 +-
.../src/main/java/org/apache/nifi/web/NiFiServiceFacade.java | 2 +-
.../java/org/apache/nifi/web/StandardNiFiServiceFacade.java | 4 +++-
.../src/main/java/org/apache/nifi/web/api/FlowResource.java | 2 +-
.../main/java/org/apache/nifi/web/api/dto/EntityFactory.java | 10 +++++-----
.../nifi/web/configuration/WebApplicationConfiguration.java | 4 ++--
.../java/org/apache/nifi/web/controller/ControllerFacade.java | 2 +-
.../org/apache/nifi/web/StandardNiFiServiceFacadeTest.java | 2 +-
.../java/org/apache/nifi/web/api/TestConnectorResource.java | 2 +-
.../web/connector/StandardNiFiConnectorWebContextTest.java | 6 +++---
.../org/apache/nifi/extension/manifest/ConfigurationStep.java | 2 +-
.../nifi/extension/manifest/ConfigurationStepDependency.java | 2 +-
.../org/apache/nifi/extension/manifest/ConnectorProperty.java | 2 +-
.../nifi/extension/manifest/ConnectorPropertyDependency.java | 2 +-
.../apache/nifi/extension/manifest/ConnectorPropertyGroup.java | 2 +-
.../runtime/manifest/impl/StandardRuntimeManifestBuilder.java | 2 +-
32 files changed, 44 insertions(+), 45 deletions(-)
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaTopicsStep.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaTopicsStep.java
index 643e4e836e..754a708c39 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaTopicsStep.java
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaTopicsStep.java
@@ -76,7 +76,7 @@ public class KafkaTopicsStep {
public static final ConfigurationStep KAFKA_TOPICS_STEP = new ConfigurationStep.Builder()
.name(STEP_NAME)
- .description("Kafka topics to consume from." )
+ .description("Kafka topics to consume from.")
.propertyGroups(List.of(
KAFKA_TOPICS_GROUP
))
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/RecordIterable.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/RecordIterable.java
index 714caa06db..6f8229eb2f 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/RecordIterable.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/RecordIterable.java
@@ -76,4 +76,4 @@ public class RecordIterable implements Iterable<ByteRecord> {
);
}
}
-}
\ No newline at end of file
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
index 205c0381f4..402ecb60a0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
@@ -93,4 +93,4 @@ public class ConfigurationStepConfigurationDTO {
public void setDependencies(final Set<ConfigurationStepDependencyDTO> dependencies) {
this.dependencies = dependencies;
}
-}
\ No newline at end of file
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/PropertyGroupConfigurationDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/PropertyGroupConfigurationDTO.java
index a28366175b..a78f946e41 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/PropertyGroupConfigurationDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/PropertyGroupConfigurationDTO.java
@@ -79,4 +79,4 @@ public class PropertyGroupConfigurationDTO {
public void setPropertyValues(final Map<String, ConnectorValueReferenceDTO> propertyValues) {
this.propertyValues = propertyValues;
}
-}
\ No newline at end of file
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorTypesEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorTypesEntity.java
index d9a8f9a287..3192d3cab6 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorTypesEntity.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorTypesEntity.java
@@ -16,10 +16,11 @@
*/
package org.apache.nifi.web.api.entity;
-import java.util.Set;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.apache.nifi.web.api.dto.DocumentedTypeDTO;
+import java.util.Set;
+
/**
* A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds a reference to a list of connector types.
*/
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
index 651b51f89d..1f2f957285 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
@@ -298,7 +298,7 @@ public final class ResourceFactory {
}
};
- private final static Resource CONNECTORS_RESOURCE = new Resource() {
+ private static final Resource CONNECTORS_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
index daafc89fbc..0eefb8b763 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
@@ -24,13 +24,13 @@ import org.apache.nifi.cluster.coordination.http.endpoints.ClearBulletinsEndpoin
import org.apache.nifi.cluster.coordination.http.endpoints.ClearBulletinsForGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ComponentStateEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionStatusEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorFlowEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorPropertyGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorPropertyGroupNamesEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorsEndpointMerger;
-import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionStatusEndpointMerger;
-import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ControllerBulletinsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ControllerConfigurationEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ControllerEndpointMerger;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupEndpointMerger.java
index 46a94ae00f..bfda6dcab8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupEndpointMerger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupEndpointMerger.java
@@ -18,8 +18,8 @@
package org.apache.nifi.cluster.coordination.http.endpoints;
import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
-import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.manager.ConfigurationStepEntityMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupNamesEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupNamesEndpointMerger.java
index 7fd64886a0..fdc932c1bb 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupNamesEndpointMerger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupNamesEndpointMerger.java
@@ -18,8 +18,8 @@
package org.apache.nifi.cluster.coordination.http.endpoints;
import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
-import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.manager.ConfigurationStepNamesEntityMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
index 49a6537007..a0b234e241 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
@@ -28,6 +28,7 @@ import org.apache.nifi.cluster.coordination.ClusterCoordinator;
import org.apache.nifi.cluster.coordination.http.HttpResponseMapper;
import org.apache.nifi.cluster.coordination.http.StandardHttpResponseMapper;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ControllerServiceEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.FlowRegistryClientEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.FunnelEndpointMerger;
@@ -37,7 +38,6 @@ import org.apache.nifi.cluster.coordination.http.endpoints.ParameterProviderEndp
import org.apache.nifi.cluster.coordination.http.endpoints.PortEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ProcessGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ProcessorEndpointMerger;
-import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.RemoteProcessGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskEndpointMerger;
import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
index 0fe3715555..e4c57c1df6 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java
@@ -19,11 +19,10 @@ package org.apache.nifi.components.connector.secrets;
import org.apache.nifi.components.connector.Secret;
import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.controller.ParameterProviderNode;
import org.apache.nifi.controller.flow.FlowManager;
-import org.apache.nifi.components.validation.ValidationStatus;
-
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
index 9723e85e16..f5c4887dad 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorRepositoryInitializationContext.java
@@ -17,8 +17,8 @@
package org.apache.nifi.components.connector;
-import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.controller.NodeTypeProvider;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.nar.ExtensionManager;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
index 1aa7a4b271..5c3d71f045 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepoInitializationContext.java
@@ -17,8 +17,8 @@
package org.apache.nifi.components.connector;
-import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.components.connector.secrets.SecretsManager;
import org.apache.nifi.controller.NodeTypeProvider;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.nar.ExtensionManager;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index 34fe990aa1..d47cb4ed2e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -187,7 +187,7 @@ public class StandardConnectorRepository implements ConnectorRepository {
throw new FlowUpdateException("State of " + connector + " transitioned to UPDATE_FAILED while waiting for state to become one of " + desiredStates);
}
- throw new FlowUpdateException("While waiting for %s to transition to state in set %s, connector transitioned to unexpected state: %s".formatted(connector, desiredStates, clusterState) );
+ throw new FlowUpdateException("While waiting for %s to transition to state in set %s, connector transitioned to unexpected state: %s".formatted(connector, desiredStates, clusterState));
}
}
@@ -231,4 +231,4 @@ public class StandardConnectorRepository implements ConnectorRepository {
public ConnectorAssetRepository getAssetRepository() {
return assetRepository;
}
-}
\ No newline at end of file
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedDataflowMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedDataflowMapper.java
index ed0479a858..abf6c4e6c4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedDataflowMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedDataflowMapper.java
@@ -26,11 +26,9 @@ import org.apache.nifi.controller.ProcessorNode;
import org.apache.nifi.controller.ReportingTaskNode;
import org.apache.nifi.controller.flow.VersionedDataflow;
import org.apache.nifi.controller.flow.VersionedFlowEncodingVersion;
-import org.apache.nifi.flow.VersionedConnector;
-import org.apache.nifi.flow.VersionedFlowAnalysisRule;
-import org.apache.nifi.flow.VersionedFlowRegistryClient;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.flow.ScheduledState;
+import org.apache.nifi.flow.VersionedConnector;
import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedFlowAnalysisRule;
import org.apache.nifi.flow.VersionedFlowRegistryClient;
@@ -44,8 +42,8 @@ import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.registry.flow.FlowRegistryClientNode;
import org.apache.nifi.registry.flow.mapping.ComponentIdLookup;
import org.apache.nifi.registry.flow.mapping.FlowMappingOptions;
-import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.registry.flow.mapping.SensitiveValueEncryptor;
+import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
import java.util.ArrayList;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
index 06c2150454..e88661cc91 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
@@ -67,7 +67,6 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
index e124b1a842..dfb94a2e77 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
@@ -29,6 +29,7 @@ import org.apache.nifi.bundle.BundleDetails;
import org.apache.nifi.components.ClassloaderIsolationKeyProvider;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.state.StateProvider;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.leader.election.LeaderElectionManager;
@@ -55,7 +56,6 @@ import org.apache.nifi.reporting.ReportingTask;
import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.nifi.components.connector.Connector;
import java.io.BufferedReader;
import java.io.File;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index fba789287f..967c0e1f29 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -157,13 +157,13 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.SnippetEntity;
import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
import org.apache.nifi.web.api.entity.UserEntity;
import org.apache.nifi.web.api.entity.UserGroupEntity;
-import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.VersionControlComponentMappingEntity;
import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
import org.apache.nifi.web.api.entity.VersionedFlowEntity;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index ae5e9ae906..1826a1efd4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -3537,7 +3537,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
final ConnectorNode node = connectorDAO.getConnector(connectorDTO.getId());
- if (connectorDTO.getName() != null) node.setName(connectorDTO.getName());
+ if (connectorDTO.getName() != null) {
+ node.setName(connectorDTO.getName());
+ }
controllerFacade.save();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
index 80fa3f80d5..9540806416 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
@@ -107,7 +107,6 @@ import org.apache.nifi.web.api.entity.AboutEntity;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.AdditionalDetailsEntity;
-import org.apache.nifi.web.api.entity.StepDocumentationEntity;
import org.apache.nifi.web.api.entity.BannerEntity;
import org.apache.nifi.web.api.entity.BulletinBoardEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsForGroupRequestEntity;
@@ -160,6 +159,7 @@ import org.apache.nifi.web.api.entity.RuntimeManifestEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
import org.apache.nifi.web.api.entity.SearchResultsEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
+import org.apache.nifi.web.api.entity.StepDocumentationEntity;
import org.apache.nifi.web.api.entity.VersionedFlowEntity;
import org.apache.nifi.web.api.entity.VersionedFlowSnapshotMetadataEntity;
import org.apache.nifi.web.api.entity.VersionedFlowSnapshotMetadataSetEntity;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
index 9d51b58e65..1e1b965a14 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
@@ -20,10 +20,6 @@ import org.apache.nifi.web.api.dto.action.ActionDTO;
import org.apache.nifi.web.api.dto.diagnostics.ProcessorDiagnosticsDTO;
import org.apache.nifi.web.api.dto.flow.FlowBreadcrumbDTO;
import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
-import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
-import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
-import org.apache.nifi.web.api.entity.ConnectorEntity;
-import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.dto.status.ConnectionStatisticsDTO;
import org.apache.nifi.web.api.dto.status.ConnectionStatisticsSnapshotDTO;
import org.apache.nifi.web.api.dto.status.ConnectionStatusDTO;
@@ -48,11 +44,15 @@ import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.apache.nifi.web.api.entity.BulletinEntity;
import org.apache.nifi.web.api.entity.ComponentReferenceEntity;
import org.apache.nifi.web.api.entity.ComponentValidationResultEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ConnectionEntity;
import org.apache.nifi.web.api.entity.ConnectionStatisticsEntity;
import org.apache.nifi.web.api.entity.ConnectionStatisticsSnapshotEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusSnapshotEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ControllerConfigurationEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentEntity;
@@ -83,8 +83,8 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusSnapshotEntity;
-import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.SnippetEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
index ef53f7b877..904fdcb9d1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
@@ -32,11 +32,10 @@ import org.apache.nifi.reporting.BulletinRepository;
import org.apache.nifi.services.FlowService;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.ContentAccess;
+import org.apache.nifi.web.NiFiConnectorWebContext;
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.NiFiServiceFacadeLock;
-import org.apache.nifi.web.NiFiConnectorWebContext;
import org.apache.nifi.web.NiFiWebConfigurationContext;
-import org.apache.nifi.web.connector.StandardNiFiConnectorWebContext;
import org.apache.nifi.web.StandardNiFiContentAccess;
import org.apache.nifi.web.StandardNiFiServiceFacade;
import org.apache.nifi.web.StandardNiFiWebConfigurationContext;
@@ -47,6 +46,7 @@ import org.apache.nifi.web.api.metrics.jmx.JmxMetricsCollector;
import org.apache.nifi.web.api.metrics.jmx.JmxMetricsResultConverter;
import org.apache.nifi.web.api.metrics.jmx.JmxMetricsService;
import org.apache.nifi.web.api.metrics.jmx.StandardJmxMetricsService;
+import org.apache.nifi.web.connector.StandardNiFiConnectorWebContext;
import org.apache.nifi.web.controller.ControllerFacade;
import org.apache.nifi.web.controller.ControllerSearchService;
import org.apache.nifi.web.dao.AccessPolicyDAO;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index dd1456288f..4dd9837660 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -44,10 +44,10 @@ import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.RequiredPermission;
-import org.apache.nifi.components.listen.ListenComponent;
import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.Secret;
+import org.apache.nifi.components.listen.ListenComponent;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.connectable.Port;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
index 016e5220ad..1779e797f2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
@@ -106,8 +106,8 @@ import org.apache.nifi.web.api.entity.ClearBulletinsForGroupResultsEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsResultEntity;
import org.apache.nifi.web.api.entity.CopyRequestEntity;
import org.apache.nifi.web.api.entity.CopyResponseEntity;
-import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
+import org.apache.nifi.web.api.entity.SecretsEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
index c218d87d68..17678723c7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
@@ -31,8 +31,8 @@ import org.apache.nifi.web.Revision;
import org.apache.nifi.web.api.dto.AllowableValueDTO;
import org.apache.nifi.web.api.dto.ConnectorDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
-import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContextTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContextTest.java
index 321e7ff02c..a10a15a761 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContextTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/connector/StandardNiFiConnectorWebContextTest.java
@@ -17,9 +17,6 @@
package org.apache.nifi.web.connector;
import org.apache.nifi.authorization.AccessDeniedException;
-import org.apache.nifi.components.connector.Connector;
-import org.apache.nifi.web.ConnectorWebMethod;
-import org.apache.nifi.web.ConnectorWebMethod.AccessType;
import org.apache.nifi.authorization.AuthorizableLookup;
import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
@@ -29,8 +26,11 @@ import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.components.connector.Connector;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.web.ConnectorWebMethod;
+import org.apache.nifi.web.ConnectorWebMethod.AccessType;
import org.apache.nifi.web.NiFiConnectorWebContext.ConnectorWebContext;
import org.apache.nifi.web.connector.authorization.AuthorizingFlowContext;
import org.apache.nifi.web.dao.ConnectorDAO;
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStep.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStep.java
index 2c704d4e1c..457e6d8fe8 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStep.java
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStep.java
@@ -17,11 +17,11 @@
package org.apache.nifi.extension.manifest;
import io.swagger.v3.oas.annotations.media.Schema;
-
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
+
import java.util.List;
/**
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStepDependency.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStepDependency.java
index a702f57675..ac4381b3e0 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStepDependency.java
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConfigurationStepDependency.java
@@ -17,11 +17,11 @@
package org.apache.nifi.extension.manifest;
import io.swagger.v3.oas.annotations.media.Schema;
-
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
+
import java.util.List;
/**
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorProperty.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorProperty.java
index f006772070..812a45a880 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorProperty.java
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorProperty.java
@@ -17,11 +17,11 @@
package org.apache.nifi.extension.manifest;
import io.swagger.v3.oas.annotations.media.Schema;
-
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
+
import java.util.List;
/**
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyDependency.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyDependency.java
index 5d649e9475..dc1fb67a6b 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyDependency.java
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyDependency.java
@@ -17,11 +17,11 @@
package org.apache.nifi.extension.manifest;
import io.swagger.v3.oas.annotations.media.Schema;
-
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
+
import java.util.List;
/**
diff --git a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyGroup.java b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyGroup.java
index 7d01827432..e6d411517c 100644
--- a/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyGroup.java
+++ b/nifi-manifest/nifi-extension-manifest-model/src/main/java/org/apache/nifi/extension/manifest/ConnectorPropertyGroup.java
@@ -17,11 +17,11 @@
package org.apache.nifi.extension.manifest;
import io.swagger.v3.oas.annotations.media.Schema;
-
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
+
import java.util.List;
/**
diff --git a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
index b36add8336..91318bac00 100644
--- a/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
+++ b/nifi-manifest/nifi-runtime-manifest-core/src/main/java/org/apache/nifi/runtime/manifest/impl/StandardRuntimeManifestBuilder.java
@@ -53,6 +53,7 @@ import org.apache.nifi.components.state.Scope;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.extension.manifest.AllowableValue;
import org.apache.nifi.extension.manifest.Attribute;
+import org.apache.nifi.extension.manifest.ConnectorProperty;
import org.apache.nifi.extension.manifest.DefaultSchedule;
import org.apache.nifi.extension.manifest.DefaultSettings;
import org.apache.nifi.extension.manifest.Dependency;
@@ -70,7 +71,6 @@ import org.apache.nifi.extension.manifest.ResourceDefinition;
import org.apache.nifi.extension.manifest.Restricted;
import org.apache.nifi.extension.manifest.Stateful;
import org.apache.nifi.extension.manifest.SystemResourceConsideration;
-import org.apache.nifi.extension.manifest.ConnectorProperty;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.runtime.manifest.ComponentManifestBuilder;
import org.apache.nifi.runtime.manifest.ExtensionManifestContainer;
(nifi) 32/40: NIFI-15376: Adding dependencies to the configuration step dto. (#10674)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 1be2089dae76671de638271a66ed0d2a08ec400a
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Thu Jan 15 11:21:24 2026 -0500
NIFI-15376: Adding dependencies to the configuration step dto. (#10674)
---
.../api/dto/ConfigurationStepConfigurationDTO.java | 14 +++++
.../api/dto/ConfigurationStepDependencyDTO.java | 70 ++++++++++++++++++++++
.../org/apache/nifi/web/api/dto/DtoFactory.java | 16 +++++
3 files changed, 100 insertions(+)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
index 4b58f9df2c..205c0381f4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
+import java.util.Set;
/**
* The configuration for a configuration step.
@@ -31,6 +32,7 @@ public class ConfigurationStepConfigurationDTO {
private String configurationStepDescription;
private boolean documented;
private List<PropertyGroupConfigurationDTO> propertyGroupConfigurations;
+ private Set<ConfigurationStepDependencyDTO> dependencies;
/**
* @return the configuration step name
@@ -79,4 +81,16 @@ public class ConfigurationStepConfigurationDTO {
public void setPropertyGroupConfigurations(final List<PropertyGroupConfigurationDTO> propertyGroupConfigurations) {
this.propertyGroupConfigurations = propertyGroupConfigurations;
}
+
+ /**
+ * @return the dependencies that this configuration step has on other configuration steps' properties
+ */
+ @Schema(description = "The dependencies that this configuration step has on other configuration steps' properties.")
+ public Set<ConfigurationStepDependencyDTO> getDependencies() {
+ return dependencies;
+ }
+
+ public void setDependencies(final Set<ConfigurationStepDependencyDTO> dependencies) {
+ this.dependencies = dependencies;
+ }
}
\ No newline at end of file
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepDependencyDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepDependencyDTO.java
new file mode 100644
index 0000000000..ee59271cfe
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepDependencyDTO.java
@@ -0,0 +1,70 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+import java.util.Set;
+
+/**
+ * A dependency that a configuration step has on another configuration step's property.
+ */
+@XmlType(name = "configurationStepDependency")
+public class ConfigurationStepDependencyDTO {
+
+ private String stepName;
+ private String propertyName;
+ private Set<String> dependentValues;
+
+ /**
+ * @return the name of the configuration step that this step depends on
+ */
+ @Schema(description = "The name of the configuration step that this step depends on.")
+ public String getStepName() {
+ return stepName;
+ }
+
+ public void setStepName(final String stepName) {
+ this.stepName = stepName;
+ }
+
+ /**
+ * @return the name of the property within the dependent step that must have a value
+ */
+ @Schema(description = "The name of the property within the dependent step that must have a value.")
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ public void setPropertyName(final String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ /**
+ * @return the values of the dependent property that satisfy this dependency, or null if any non-null value satisfies the dependency
+ */
+ @Schema(description = "The values of the dependent property that satisfy this dependency. If null, any non-null value satisfies the dependency.")
+ public Set<String> getDependentValues() {
+ return dependentValues;
+ }
+
+ public void setDependentValues(final Set<String> dependentValues) {
+ this.dependentValues = dependentValues;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 652cf93ffd..4b2ec0e9dd 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -68,6 +68,7 @@ import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConfigurationStepDependency;
import org.apache.nifi.components.connector.ConnectorAction;
import org.apache.nifi.components.connector.ConnectorAssetRepository;
import org.apache.nifi.components.connector.ConnectorConfiguration;
@@ -5350,6 +5351,21 @@ public final class DtoFactory {
.map(propertyGroup -> createPropertyGroupConfigurationDtoFromGroup(propertyGroup, stepConfig))
.collect(Collectors.toList());
dto.setPropertyGroupConfigurations(propertyGroupDtos);
+
+ // Convert step dependencies
+ final Set<ConfigurationStepDependencyDTO> dependencyDtos = step.getDependencies().stream()
+ .map(this::createConfigurationStepDependencyDto)
+ .collect(Collectors.toSet());
+ dto.setDependencies(dependencyDtos);
+
+ return dto;
+ }
+
+ private ConfigurationStepDependencyDTO createConfigurationStepDependencyDto(final ConfigurationStepDependency dependency) {
+ final ConfigurationStepDependencyDTO dto = new ConfigurationStepDependencyDTO();
+ dto.setStepName(dependency.getStepName());
+ dto.setPropertyName(dependency.getPropertyName());
+ dto.setDependentValues(dependency.getDependentValues());
return dto;
}
(nifi) 05/40: NIFI-15259: Added REST API for Connectors
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit c3393ee65f4364621fb47b404feda2c9bbc7787c
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Fri Oct 24 15:15:12 2025 -0400
NIFI-15259: Added REST API for Connectors
Introduced comprehensive REST endpoints for managing Connectors through
the NiFi web interface and API clients.
Key capabilities:
- Full lifecycle management (create, read, update, delete)
- Start/stop control with run status endpoints
- Multi-step configuration with property groups supporting string
literals, asset references, and secret references
- Asynchronous configuration verification with polling support
- Access to connector's managed flow and process group status
- Search functionality within connector's encapsulated flow
Cluster support:
- Request replication across cluster nodes
- Response merging for dynamic property allowable values that may
differ between nodes
- Consistent configuration step and property descriptor handling
Infrastructure:
- Audit logging for all connector configuration changes
- Authorization integration with NiFi's policy framework
- UI extension support for connector-specific interfaces
- Frontend updates for connector access policy management
---
nifi-assembly/pom.xml | 6 +
.../src/main/asciidoc/administration-guide.adoc | 5 +
.../java/org/apache/nifi/web/UiExtensionType.java | 1 +
.../web/api/dto/ConfigVerificationResultDTO.java | 10 +
.../api/dto/ConfigurationStepConfigurationDTO.java | 69 +
.../web/api/dto/ConnectorConfigurationDTO.java | 43 +
.../org/apache/nifi/web/api/dto/ConnectorDTO.java | 141 ++
.../api/dto/ConnectorPropertyDependencyDTO.java | 57 +
.../api/dto/ConnectorPropertyDescriptorDTO.java | 137 ++
.../web/api/dto/ConnectorValueReferenceDTO.java | 108 ++
.../web/api/dto/PropertyGroupConfigurationDTO.java | 82 ++
.../dto/VerifyConnectorConfigStepRequestDTO.java | 69 +
.../web/api/dto/status/ConnectorStatusDTO.java | 146 +++
.../web/api/entity/ConfigurationStepEntity.java | 77 ++
.../api/entity/ConfigurationStepNamesEntity.java | 44 +
.../nifi/web/api/entity/ConnectorEntity.java | 63 +
.../ConnectorPropertyAllowableValuesEntity.java | 71 +
.../web/api/entity/ConnectorRunStatusEntity.java | 34 +-
.../nifi/web/api/entity/ConnectorTypesEntity.java} | 33 +-
.../nifi/web/api/entity/ConnectorsEntity.java | 55 +
.../nifi/web/api/entity/CurrentUserEntity.java | 13 +
.../VerifyConnectorConfigStepRequestEntity.java | 33 +-
.../authorization/resource/ResourceFactory.java | 27 +
.../http/StandardHttpResponseMapper.java | 12 +
.../http/endpoints/ConnectorEndpointMerger.java | 60 +
.../endpoints/ConnectorFlowEndpointMerger.java | 56 +
.../ConnectorPropertyGroupEndpointMerger.java | 51 +
.../ConnectorPropertyGroupNamesEndpointMerger.java | 63 +
.../http/endpoints/ConnectorsEndpointMerger.java | 66 +
.../http/endpoints/CurrentUserEndpointMerger.java | 1 +
.../coordination/http/endpoints/FlowMerger.java | 11 +
.../VerifyConnectorConfigStepEndpointMerger.java | 72 ++
.../replication/ThreadPoolRequestReplicator.java | 2 +
.../manager/ConfigurationStepEntityMerger.java | 160 +++
.../ConfigurationStepNamesEntityMerger.java | 62 +
.../cluster/manager/ConnectorEntityMerger.java | 128 ++
.../ConnectorPropertyDescriptorDtoMerger.java | 64 +
.../cluster/manager/ConnectorsEntityMerger.java} | 38 +-
.../endpoints/CurrentUserEndpointMergerTest.java | 4 +
.../manager/ConfigurationStepEntityMergerTest.java | 184 +++
.../cluster/manager/ConnectorEntityMergerTest.java | 232 ++++
.../ConnectorPropertyDescriptorDtoMergerTest.java | 171 +++
.../apache/nifi/controller/ExtensionBuilder.java | 37 +-
.../org/apache/nifi/web/server/JettyServer.java | 117 +-
.../org/apache/nifi/ui/extension/UiExtension.java | 16 +
.../org/apache/nifi/audit/ConnectorAuditor.java | 492 +++++++
.../nifi/authorization/AuthorizableLookup.java | 6 +
.../authorization/StandardAuthorizableLookup.java | 29 +-
.../org/apache/nifi/web/NiFiServiceFacade.java | 58 +
.../apache/nifi/web/NiFiWebApiResourceConfig.java | 1 +
.../nifi/web/StandardNiFiConnectorWebContext.java | 44 +
.../apache/nifi/web/StandardNiFiServiceFacade.java | 271 +++-
.../org/apache/nifi/web/api/ConnectorResource.java | 1354 ++++++++++++++++++++
.../java/org/apache/nifi/web/api/FlowResource.java | 110 +-
.../org/apache/nifi/web/api/dto/DtoFactory.java | 215 ++++
.../org/apache/nifi/web/api/dto/EntityFactory.java | 78 ++
.../configuration/WebApplicationConfiguration.java | 17 +
.../nifi/web/controller/ControllerFacade.java | 42 +
.../java/org/apache/nifi/web/dao/ConnectorDAO.java | 59 +
.../nifi/web/dao/impl/StandardConnectorDAO.java | 203 +++
.../nifi/web/StandardNiFiServiceFacadeTest.java | 36 +
.../apache/nifi/web/api/TestConnectorResource.java | 387 ++++++
.../org/apache/nifi/web/api/TestFlowResource.java | 44 +-
.../nifi/web/controller/ControllerFacadeTest.java | 104 ++
.../web/dao/impl/StandardConnectorDAOTest.java | 213 +++
.../component-access-policies.component.ts | 6 +-
.../global-access-policies.component.ts | 1 +
.../app/state/current-user/current-user.reducer.ts | 1 +
.../apps/nifi/src/app/state/current-user/index.ts | 1 +
.../shared/src/services/nifi-common.service.ts | 5 +
70 files changed, 6617 insertions(+), 91 deletions(-)
diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index 97d8b1e688..16787bda28 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -471,6 +471,12 @@ language governing permissions and limitations under the License. -->
<version>2.8.0-SNAPSHOT</version>
<type>nar</type>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-to-s3-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-groovyx-nar</artifactId>
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 869431aab0..b34c6f7d39 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -1510,6 +1510,11 @@ Global access policies govern the following system level authorizations:
|Parameter Contexts
|`/parameter-contexts`
+|access connectors
+|Allows users to view/modify Connectors
+|N/A
+|`/connectors`
+
|query provenance
|Allows users to submit a Provenance Search and request Event Lineage
|Data Provenance
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
index 17c2d91e2d..3cde479f0f 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
@@ -31,4 +31,5 @@ public enum UiExtensionType {
ReportingTaskConfiguration,
ParameterProviderConfiguration,
FlowRegistryClientConfiguration,
+ Connector,
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigVerificationResultDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigVerificationResultDTO.java
index 13841ca76f..8134edc503 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigVerificationResultDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigVerificationResultDTO.java
@@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class ConfigVerificationResultDTO {
private String outcome;
private String verificationStepName;
+ private String subject;
private String explanation;
@Schema(description = "The outcome of the verification", allowableValues = {"SUCCESSFUL", "FAILED", "SKIPPED"})
@@ -42,6 +43,15 @@ public class ConfigVerificationResultDTO {
this.verificationStepName = verificationStepName;
}
+ @Schema(description = "The subject of the verification step")
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(final String subject) {
+ this.subject = subject;
+ }
+
@Schema(description = "An explanation of why the step was or was not successful")
public String getExplanation() {
return explanation;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
new file mode 100644
index 0000000000..9dd55b524c
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
@@ -0,0 +1,69 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+import java.util.List;
+
+/**
+ * The configuration for a configuration step.
+ */
+@XmlType(name = "configurationStepConfiguration")
+public class ConfigurationStepConfigurationDTO {
+
+ private String configurationStepName;
+ private String configurationStepDescription;
+ private List<PropertyGroupConfigurationDTO> propertyGroupConfigurations;
+
+ /**
+ * @return the configuration step name
+ */
+ @Schema(description = "The name of the configuration step.")
+ public String getConfigurationStepName() {
+ return configurationStepName;
+ }
+
+ public void setConfigurationStepName(final String configurationStepName) {
+ this.configurationStepName = configurationStepName;
+ }
+
+ /**
+ * @return the configuration step description
+ */
+ @Schema(description = "The description of the configuration step.")
+ public String getConfigurationStepDescription() {
+ return configurationStepDescription;
+ }
+
+ public void setConfigurationStepDescription(final String configurationStepDescription) {
+ this.configurationStepDescription = configurationStepDescription;
+ }
+
+ /**
+ * @return the property group configurations
+ */
+ @Schema(description = "The list of property group configurations for this configuration step.")
+ public List<PropertyGroupConfigurationDTO> getPropertyGroupConfigurations() {
+ return propertyGroupConfigurations;
+ }
+
+ public void setPropertyGroupConfigurations(final List<PropertyGroupConfigurationDTO> propertyGroupConfigurations) {
+ this.propertyGroupConfigurations = propertyGroupConfigurations;
+ }
+}
\ No newline at end of file
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorConfigurationDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorConfigurationDTO.java
new file mode 100644
index 0000000000..6ba11d535c
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorConfigurationDTO.java
@@ -0,0 +1,43 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+import java.util.List;
+
+/**
+ * The configuration for a Connector.
+ */
+@XmlType(name = "connectorConfiguration")
+public class ConnectorConfigurationDTO {
+
+ private List<ConfigurationStepConfigurationDTO> configurationStepConfigurations;
+
+ /**
+ * @return the configuration step configurations
+ */
+ @Schema(description = "The list of configuration step configurations for this Connector.")
+ public List<ConfigurationStepConfigurationDTO> getConfigurationStepConfigurations() {
+ return configurationStepConfigurations;
+ }
+
+ public void setConfigurationStepConfigurations(final List<ConfigurationStepConfigurationDTO> configurationStepConfigurations) {
+ this.configurationStepConfigurations = configurationStepConfigurations;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
new file mode 100644
index 0000000000..9aed99c62b
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorDTO.java
@@ -0,0 +1,141 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+import java.util.Collection;
+
+/**
+ * Component representing a Connector instance.
+ */
+@XmlType(name = "connector")
+public class ConnectorDTO extends ComponentDTO {
+ private String name;
+ private String type;
+ private BundleDTO bundle;
+ private String state; // RUNNING, STOPPED, DISABLED
+ private ConnectorConfigurationDTO activeConfiguration;
+ private ConnectorConfigurationDTO workingConfiguration;
+
+ private Collection<String> validationErrors;
+ private String validationStatus;
+ private Boolean multipleVersionsAvailable;
+
+ private String configurationUrl;
+ private String detailsUrl;
+
+ @Schema(description = "The name of the Connector.")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ @Schema(description = "The fully qualified type of the Connector.")
+ public String getType() {
+ return type;
+ }
+
+ public void setType(final String type) {
+ this.type = type;
+ }
+
+ @Schema(description = "The details of the artifact that bundled this Connector type.")
+ public BundleDTO getBundle() {
+ return bundle;
+ }
+
+ public void setBundle(final BundleDTO bundle) {
+ this.bundle = bundle;
+ }
+
+ @Schema(description = "The state of the Connector.", allowableValues = {"RUNNING", "STOPPED", "DISABLED"})
+ public String getState() {
+ return state;
+ }
+
+ public void setState(final String state) {
+ this.state = state;
+ }
+
+ @Schema(description = "The active configuration of the Connector.")
+ public ConnectorConfigurationDTO getActiveConfiguration() {
+ return activeConfiguration;
+ }
+
+ public void setActiveConfiguration(final ConnectorConfigurationDTO activeConfiguration) {
+ this.activeConfiguration = activeConfiguration;
+ }
+
+ @Schema(description = "The working configuration of the Connector.")
+ public ConnectorConfigurationDTO getWorkingConfiguration() {
+ return workingConfiguration;
+ }
+
+ public void setWorkingConfiguration(final ConnectorConfigurationDTO workingConfiguration) {
+ this.workingConfiguration = workingConfiguration;
+ }
+
+ @Schema(description = "The validation errors for the connector.")
+ public Collection<String> getValidationErrors() {
+ return validationErrors;
+ }
+
+ public void setValidationErrors(final Collection<String> validationErrors) {
+ this.validationErrors = validationErrors;
+ }
+
+ @Schema(description = "Indicates whether the Connector is valid, invalid, or still in the process of validating")
+ public String getValidationStatus() {
+ return validationStatus;
+ }
+
+ public void setValidationStatus(final String validationStatus) {
+ this.validationStatus = validationStatus;
+ }
+
+ @Schema(description = "Whether multiple versions of this connector are available.")
+ public Boolean getMultipleVersionsAvailable() {
+ return multipleVersionsAvailable;
+ }
+
+ public void setMultipleVersionsAvailable(final Boolean multipleVersionsAvailable) {
+ this.multipleVersionsAvailable = multipleVersionsAvailable;
+ }
+
+ @Schema(description = "The URL for this connector's configuration/wizard custom UI, if applicable.")
+ public String getConfigurationUrl() {
+ return configurationUrl;
+ }
+
+ public void setConfigurationUrl(final String configurationUrl) {
+ this.configurationUrl = configurationUrl;
+ }
+
+ @Schema(description = "The URL for this connector's details custom UI, if applicable.")
+ public String getDetailsUrl() {
+ return detailsUrl;
+ }
+
+ public void setDetailsUrl(final String detailsUrl) {
+ this.detailsUrl = detailsUrl;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorPropertyDependencyDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorPropertyDependencyDTO.java
new file mode 100644
index 0000000000..2604448a7a
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorPropertyDependencyDTO.java
@@ -0,0 +1,57 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+import java.util.Set;
+
+/**
+ * A dependency that a connector property has on another property.
+ */
+@XmlType(name = "connectorPropertyDependency")
+public class ConnectorPropertyDependencyDTO {
+
+ private String propertyName;
+ private Set<String> dependentValues;
+
+ /**
+ * @return the name of the property that this property depends on
+ */
+ @Schema(description = "The name of the property that this property depends on.")
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ public void setPropertyName(final String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ /**
+ * @return the values of the dependent property that must be set for this property to be applicable
+ */
+ @Schema(description = "The values of the dependent property that must be set for this property to be applicable.")
+ public Set<String> getDependentValues() {
+ return dependentValues;
+ }
+
+ public void setDependentValues(final Set<String> dependentValues) {
+ this.dependentValues = dependentValues;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorPropertyDescriptorDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorPropertyDescriptorDTO.java
new file mode 100644
index 0000000000..edc1e9c780
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorPropertyDescriptorDTO.java
@@ -0,0 +1,137 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A description of a connector property.
+ */
+@XmlType(name = "connectorPropertyDescriptor")
+public class ConnectorPropertyDescriptorDTO {
+
+ private String name;
+ private String description;
+ private String defaultValue;
+ private Boolean required;
+ private String type;
+ private List<AllowableValueEntity> allowableValues;
+ private Boolean allowableValuesFetchable;
+ private Set<ConnectorPropertyDependencyDTO> dependencies;
+
+ /**
+ * @return property name
+ */
+ @Schema(description = "The name of the property.")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return An explanation of the meaning of the given property
+ */
+ @Schema(description = "The description of the property.")
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ /**
+ * @return default value for this property
+ */
+ @Schema(description = "The default value for the property.")
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public void setDefaultValue(final String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ /**
+ * @return whether this property is required
+ */
+ @Schema(description = "Whether the property is required.")
+ public Boolean getRequired() {
+ return required;
+ }
+
+ public void setRequired(final Boolean required) {
+ this.required = required;
+ }
+
+ /**
+ * @return the property type (STRING, INTEGER, BOOLEAN, DOUBLE, STRING_LIST, etc.)
+ */
+ @Schema(description = "The type of the property (STRING, INTEGER, BOOLEAN, DOUBLE, STRING_LIST).")
+ public String getType() {
+ return type;
+ }
+
+ public void setType(final String type) {
+ this.type = type;
+ }
+
+ /**
+ * @return set of allowable values for this property. If empty then the allowable values are not constrained
+ */
+ @Schema(description = "Allowable values for the property. If empty then the allowed values are not constrained.")
+ public List<AllowableValueEntity> getAllowableValues() {
+ return allowableValues;
+ }
+
+ public void setAllowableValues(final List<AllowableValueEntity> allowableValues) {
+ this.allowableValues = allowableValues;
+ }
+
+ /**
+ * @return whether the allowable values can be fetched dynamically
+ */
+ @Schema(description = "Whether the allowable values are dynamically fetchable based on other property values.")
+ public Boolean getAllowableValuesFetchable() {
+ return allowableValuesFetchable;
+ }
+
+ public void setAllowableValuesFetchable(final Boolean allowableValuesFetchable) {
+ this.allowableValuesFetchable = allowableValuesFetchable;
+ }
+
+ /**
+ * @return the dependencies this property has on other properties
+ */
+ @Schema(description = "The dependencies this property has on other properties.")
+ public Set<ConnectorPropertyDependencyDTO> getDependencies() {
+ return dependencies;
+ }
+
+ public void setDependencies(final Set<ConnectorPropertyDependencyDTO> dependencies) {
+ this.dependencies = dependencies;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java
new file mode 100644
index 0000000000..10d18823fd
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConnectorValueReferenceDTO.java
@@ -0,0 +1,108 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+/**
+ * A reference to a connector property value, which includes the value and its type.
+ * The value can be a string literal, a reference to an asset, or a reference to a secret.
+ */
+@XmlType(name = "connectorValueReference")
+public class ConnectorValueReferenceDTO {
+
+ private String valueType;
+ private String value;
+ private String assetIdentifier;
+ private String secretProviderId;
+ private String secretProviderName;
+ private String secretName;
+
+ /**
+ * @return the type of value (STRING_LITERAL, ASSET_REFERENCE, or SECRET_REFERENCE)
+ */
+ @Schema(description = "The type of value (STRING_LITERAL, ASSET_REFERENCE, or SECRET_REFERENCE).")
+ public String getValueType() {
+ return valueType;
+ }
+
+ public void setValueType(final String valueType) {
+ this.valueType = valueType;
+ }
+
+ /**
+ * @return the string literal value when valueType is STRING_LITERAL
+ */
+ @Schema(description = "The string literal value. Applicable when valueType is STRING_LITERAL.")
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(final String value) {
+ this.value = value;
+ }
+
+ /**
+ * @return the asset identifier when valueType is ASSET_REFERENCE
+ */
+ @Schema(description = "The asset identifier. Applicable when valueType is ASSET_REFERENCE.")
+ public String getAssetIdentifier() {
+ return assetIdentifier;
+ }
+
+ public void setAssetIdentifier(final String assetIdentifier) {
+ this.assetIdentifier = assetIdentifier;
+ }
+
+ /**
+ * @return the secret provider identifier when valueType is SECRET_REFERENCE
+ */
+ @Schema(description = "The secret provider identifier. Applicable when valueType is SECRET_REFERENCE.")
+ public String getSecretProviderId() {
+ return secretProviderId;
+ }
+
+ public void setSecretProviderId(final String secretProviderId) {
+ this.secretProviderId = secretProviderId;
+ }
+
+ /**
+ * @return the secret provider name when valueType is SECRET_REFERENCE
+ */
+ @Schema(description = "The secret provider name. Applicable when valueType is SECRET_REFERENCE.")
+ public String getSecretProviderName() {
+ return secretProviderName;
+ }
+
+ public void setSecretProviderName(final String secretProviderName) {
+ this.secretProviderName = secretProviderName;
+ }
+
+ /**
+ * @return the secret name when valueType is SECRET_REFERENCE
+ */
+ @Schema(description = "The secret name. Applicable when valueType is SECRET_REFERENCE.")
+ public String getSecretName() {
+ return secretName;
+ }
+
+ public void setSecretName(final String secretName) {
+ this.secretName = secretName;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/PropertyGroupConfigurationDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/PropertyGroupConfigurationDTO.java
new file mode 100644
index 0000000000..a28366175b
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/PropertyGroupConfigurationDTO.java
@@ -0,0 +1,82 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+import java.util.Map;
+
+/**
+ * The configuration for a property group.
+ */
+@XmlType(name = "propertyGroupConfiguration")
+public class PropertyGroupConfigurationDTO {
+
+ private String propertyGroupName;
+ private String propertyGroupDescription;
+ private Map<String, ConnectorPropertyDescriptorDTO> propertyDescriptors;
+ private Map<String, ConnectorValueReferenceDTO> propertyValues;
+
+ /**
+ * @return the property group name
+ */
+ @Schema(description = "The name of the property group.")
+ public String getPropertyGroupName() {
+ return propertyGroupName;
+ }
+
+ public void setPropertyGroupName(final String propertyGroupName) {
+ this.propertyGroupName = propertyGroupName;
+ }
+
+ /**
+ * @return the property group description
+ */
+ @Schema(description = "The description of the property group.")
+ public String getPropertyGroupDescription() {
+ return propertyGroupDescription;
+ }
+
+ public void setPropertyGroupDescription(final String propertyGroupDescription) {
+ this.propertyGroupDescription = propertyGroupDescription;
+ }
+
+ /**
+ * @return the property descriptors keyed by property name
+ */
+ @Schema(description = "The property descriptors for this property group, keyed by property name.")
+ public Map<String, ConnectorPropertyDescriptorDTO> getPropertyDescriptors() {
+ return propertyDescriptors;
+ }
+
+ public void setPropertyDescriptors(final Map<String, ConnectorPropertyDescriptorDTO> propertyDescriptors) {
+ this.propertyDescriptors = propertyDescriptors;
+ }
+
+ /**
+ * @return the property values
+ */
+ @Schema(description = "The property values for this property group.")
+ public Map<String, ConnectorValueReferenceDTO> getPropertyValues() {
+ return propertyValues;
+ }
+
+ public void setPropertyValues(final Map<String, ConnectorValueReferenceDTO> propertyValues) {
+ this.propertyValues = propertyValues;
+ }
+}
\ No newline at end of file
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyConnectorConfigStepRequestDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyConnectorConfigStepRequestDTO.java
new file mode 100644
index 0000000000..c42edd31c0
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyConnectorConfigStepRequestDTO.java
@@ -0,0 +1,69 @@
+/*
+ * 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.web.api.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
+import jakarta.xml.bind.annotation.XmlType;
+
+import java.util.List;
+
+@XmlType(name = "verifyConnectorConfigStepRequest")
+public class VerifyConnectorConfigStepRequestDTO extends AsynchronousRequestDTO<VerifyConfigUpdateStepDTO> {
+
+ private String connectorId;
+ private String configurationStepName;
+ private ConfigurationStepConfigurationDTO configurationStep;
+ private List<ConfigVerificationResultDTO> results;
+
+ @Schema(description = "The ID of the connector whose configuration step is being verified")
+ public String getConnectorId() {
+ return connectorId;
+ }
+
+ public void setConnectorId(final String connectorId) {
+ this.connectorId = connectorId;
+ }
+
+ @Schema(description = "The name of the configuration step being verified")
+ public String getConfigurationStepName() {
+ return configurationStepName;
+ }
+
+ public void setConfigurationStepName(final String configurationStepName) {
+ this.configurationStepName = configurationStepName;
+ }
+
+ @Schema(description = "The configuration step being verified")
+ public ConfigurationStepConfigurationDTO getConfigurationStep() {
+ return configurationStep;
+ }
+
+ public void setConfigurationStep(final ConfigurationStepConfigurationDTO configurationStep) {
+ this.configurationStep = configurationStep;
+ }
+
+ @Schema(description = "The results of the verification", accessMode = AccessMode.READ_ONLY)
+ public List<ConfigVerificationResultDTO> getResults() {
+ return results;
+ }
+
+ public void setResults(final List<ConfigVerificationResultDTO> results) {
+ this.results = results;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ConnectorStatusDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ConnectorStatusDTO.java
new file mode 100644
index 0000000000..ad19976d45
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ConnectorStatusDTO.java
@@ -0,0 +1,146 @@
+/*
+ * 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.web.api.dto.status;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+/**
+ * DTO for serializing the status of a connector.
+ */
+@XmlType(name = "connectorStatus")
+public class ConnectorStatusDTO extends ComponentStatusDTO {
+
+ private String id;
+ private String groupId;
+ private String name;
+ private String type;
+ private String runStatus;
+ private String validationStatus;
+ private Integer activeThreadCount;
+
+ /**
+ * The id of the connector.
+ *
+ * @return The connector id
+ */
+ @Schema(description = "The id of the connector."
+ )
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ /**
+ * The id of the group this connector belongs to.
+ *
+ * @return The group id
+ */
+ @Schema(description = "The id of the group this connector belongs to."
+ )
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(final String groupId) {
+ this.groupId = groupId;
+ }
+
+ /**
+ * The name of the connector.
+ *
+ * @return The connector name
+ */
+ @Schema(description = "The name of the connector."
+ )
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * The type of the connector.
+ *
+ * @return The connector type
+ */
+ @Schema(description = "The type of the connector."
+ )
+ public String getType() {
+ return type;
+ }
+
+ public void setType(final String type) {
+ this.type = type;
+ }
+
+ /**
+ * The run status of the connector.
+ *
+ * @return The run status
+ */
+ @Override
+ @Schema(description = "The run status of the connector."
+ )
+ public String getRunStatus() {
+ return runStatus;
+ }
+
+ @Override
+ public void setRunStatus(final String runStatus) {
+ this.runStatus = runStatus;
+ }
+
+ /**
+ * The validation status of the connector.
+ *
+ * @return The validation status
+ */
+ @Override
+ @Schema(description = "The validation status of the connector."
+ )
+ public String getValidationStatus() {
+ return validationStatus;
+ }
+
+ @Override
+ public void setValidationStatus(final String validationStatus) {
+ this.validationStatus = validationStatus;
+ }
+
+ /**
+ * The number of active threads for the connector.
+ *
+ * @return The active thread count
+ */
+ @Override
+ @Schema(description = "The number of active threads for the connector."
+ )
+ public Integer getActiveThreadCount() {
+ return activeThreadCount;
+ }
+
+ @Override
+ public void setActiveThreadCount(final Integer activeThreadCount) {
+ this.activeThreadCount = activeThreadCount;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConfigurationStepEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConfigurationStepEntity.java
new file mode 100644
index 0000000000..689b1ddf96
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConfigurationStepEntity.java
@@ -0,0 +1,77 @@
+/*
+ * 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.web.api.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+
+/**
+ * A serialized representation of this class can be placed in the entity body of a request or response to or from the API.
+ * This particular entity holds a reference to a ConfigurationStepConfigurationDTO.
+ * Note that this entity does not extend ComponentEntity because a configuration step is not itself an updatable component.
+ * Instead, it is updated through the parent Connector, so the client must provide the parent connector ID and revision.
+ * Permissions are not included on this entity since they are managed at the parent Connector level.
+ */
+@XmlRootElement(name = "configurationStepEntity")
+public class ConfigurationStepEntity extends Entity {
+
+ private ConfigurationStepConfigurationDTO configurationStep;
+ private String parentConnectorId;
+ private RevisionDTO parentConnectorRevision;
+ private Boolean disconnectedNodeAcknowledged;
+
+ /**
+ * @return the configuration step configuration
+ */
+ @Schema(description = "The configuration step configuration.")
+ public ConfigurationStepConfigurationDTO getConfigurationStep() {
+ return configurationStep;
+ }
+
+ public void setConfigurationStep(final ConfigurationStepConfigurationDTO configurationStep) {
+ this.configurationStep = configurationStep;
+ }
+
+ @Schema(description = "The id of the parent connector.")
+ public String getParentConnectorId() {
+ return parentConnectorId;
+ }
+
+ public void setParentConnectorId(final String parentConnectorId) {
+ this.parentConnectorId = parentConnectorId;
+ }
+
+ @Schema(description = "The revision of the parent connector that this configuration step belongs to.")
+ public RevisionDTO getParentConnectorRevision() {
+ return parentConnectorRevision;
+ }
+
+ public void setParentConnectorRevision(final RevisionDTO parentConnectorRevision) {
+ this.parentConnectorRevision = parentConnectorRevision;
+ }
+
+ @Schema(description = "Acknowledges that this node is disconnected to allow for mutable requests to proceed.")
+ public Boolean isDisconnectedNodeAcknowledged() {
+ return disconnectedNodeAcknowledged;
+ }
+
+ public void setDisconnectedNodeAcknowledged(final Boolean disconnectedNodeAcknowledged) {
+ this.disconnectedNodeAcknowledged = disconnectedNodeAcknowledged;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConfigurationStepNamesEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConfigurationStepNamesEntity.java
new file mode 100644
index 0000000000..0dc2da0536
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConfigurationStepNamesEntity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.web.api.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlRootElement;
+
+import java.util.List;
+
+/**
+ * A serialized representation of this class can be placed in the entity body of a request or response to or from the API.
+ * This particular entity holds a reference to a list of configuration step names.
+ */
+@XmlRootElement(name = "configurationStepNamesEntity")
+public class ConfigurationStepNamesEntity extends Entity {
+
+ private List<String> configurationStepNames;
+
+ /**
+ * @return the configuration step names
+ */
+ @Schema(description = "The list of configuration step names for a connector.")
+ public List<String> getConfigurationStepNames() {
+ return configurationStepNames;
+ }
+
+ public void setConfigurationStepNames(final List<String> configurationStepNames) {
+ this.configurationStepNames = configurationStepNames;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorEntity.java
new file mode 100644
index 0000000000..1e533823fa
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorEntity.java
@@ -0,0 +1,63 @@
+/*
+ * 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.web.api.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlType;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
+import org.apache.nifi.web.api.dto.status.ConnectorStatusDTO;
+
+@XmlRootElement(name = "connectorEntity")
+@XmlType(name = "connectorEntity")
+public class ConnectorEntity extends ComponentEntity implements Permissible<ConnectorDTO>, OperationPermissible {
+ private ConnectorDTO component;
+ private ConnectorStatusDTO status;
+
+ @Override
+ @Schema(description = "The Connector DTO")
+ public ConnectorDTO getComponent() {
+ return component;
+ }
+
+ @Override
+ public void setComponent(final ConnectorDTO component) {
+ this.component = component;
+ }
+
+ @Override
+ public PermissionsDTO getOperatePermissions() {
+ return getPermissions();
+ }
+
+ @Override
+ public void setOperatePermissions(final PermissionsDTO permissions) {
+ setPermissions(permissions);
+ }
+
+ @Schema(description = "The status of the connector.")
+ public ConnectorStatusDTO getStatus() {
+ return status;
+ }
+
+ public void setStatus(final ConnectorStatusDTO status) {
+ this.status = status;
+ }
+}
+
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorPropertyAllowableValuesEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorPropertyAllowableValuesEntity.java
new file mode 100644
index 0000000000..a2be3a0483
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorPropertyAllowableValuesEntity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.web.api.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlRootElement;
+
+import java.util.List;
+
+/**
+ * Entity for holding allowable values for a connector property.
+ */
+@XmlRootElement(name = "connectorPropertyAllowableValuesEntity")
+public class ConnectorPropertyAllowableValuesEntity extends Entity {
+
+ private String configurationStepName;
+ private String propertyGroupName;
+ private String propertyName;
+ private List<AllowableValueEntity> allowableValues;
+
+ @Schema(description = "The name of the configuration step.")
+ public String getConfigurationStepName() {
+ return configurationStepName;
+ }
+
+ public void setConfigurationStepName(final String configurationStepName) {
+ this.configurationStepName = configurationStepName;
+ }
+
+ @Schema(description = "The name of the property group.")
+ public String getPropertyGroupName() {
+ return propertyGroupName;
+ }
+
+ public void setPropertyGroupName(final String propertyGroupName) {
+ this.propertyGroupName = propertyGroupName;
+ }
+
+ @Schema(description = "The name of the property.")
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ public void setPropertyName(final String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ @Schema(description = "The allowable values for the property.")
+ public List<AllowableValueEntity> getAllowableValues() {
+ return allowableValues;
+ }
+
+ public void setAllowableValues(final List<AllowableValueEntity> allowableValues) {
+ this.allowableValues = allowableValues;
+ }
+}
+
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorRunStatusEntity.java
similarity index 55%
copy from nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
copy to nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorRunStatusEntity.java
index 17c2d91e2d..cd08a7fa76 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorRunStatusEntity.java
@@ -14,21 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.web;
+package org.apache.nifi.web.api.entity;
-/**
- * Types of UI extensions. Since a UI extension could support multiple types of
- * custom UIs it will need to include the type so the framework can appropriate
- * understand and process the request (recording actions in the audit database,
- * replicating a request throughout the cluster to the appropriate endpoints,
- * etc).
- */
-public enum UiExtensionType {
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlType;
+
+@XmlType(name = "connectorRunStatus")
+public class ConnectorRunStatusEntity extends ComponentRunStatusEntity {
+ private static final String[] SUPPORTED_STATE = {"RUNNING", "STOPPED", "DISABLED"};
+
+ @Override
+ protected String[] getSupportedState() {
+ return SUPPORTED_STATE;
+ }
- ContentViewer,
- ProcessorConfiguration,
- ControllerServiceConfiguration,
- ReportingTaskConfiguration,
- ParameterProviderConfiguration,
- FlowRegistryClientConfiguration,
+ @Schema(description = "The run status of the Connector.", allowableValues = {"RUNNING", "STOPPED", "DISABLED"})
+ @Override
+ public String getState() {
+ return super.getState();
+ }
}
+
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorTypesEntity.java
similarity index 52%
copy from nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java
copy to nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorTypesEntity.java
index da921b4535..d9a8f9a287 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorTypesEntity.java
@@ -14,35 +14,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.ui.extension;
+package org.apache.nifi.web.api.entity;
-import org.apache.nifi.web.UiExtensionType;
+import java.util.Set;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import org.apache.nifi.web.api.dto.DocumentedTypeDTO;
/**
- * Information about a UI extension required to be invoked.
+ * A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds a reference to a list of connector types.
*/
-public class UiExtension {
+@XmlRootElement(name = "connectorTypesEntity")
+public class ConnectorTypesEntity extends Entity {
- private final UiExtensionType extensionType;
- private final String contextPath;
-
- public UiExtension(final UiExtensionType extensionType, final String contextPath) {
- this.extensionType = extensionType;
- this.contextPath = contextPath;
- }
+ private Set<DocumentedTypeDTO> connectorTypes;
/**
- * @return type of this UI extension
+ * @return list of connector types that are being serialized
*/
- public UiExtensionType getExtensionType() {
- return extensionType;
+ public Set<DocumentedTypeDTO> getConnectorTypes() {
+ return connectorTypes;
}
- /**
- * @return The context path of this UI extension
- */
- public String getContextPath() {
- return contextPath;
+ public void setConnectorTypes(final Set<DocumentedTypeDTO> connectorTypes) {
+ this.connectorTypes = connectorTypes;
}
}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorsEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorsEntity.java
new file mode 100644
index 0000000000..46219d6b12
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ConnectorsEntity.java
@@ -0,0 +1,55 @@
+/*
+ * 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.web.api.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlType;
+import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.apache.nifi.web.api.dto.util.TimeAdapter;
+
+import java.util.Date;
+import java.util.Set;
+
+@XmlRootElement(name = "connectorsEntity")
+@XmlType(name = "connectorsEntity")
+public class ConnectorsEntity extends Entity {
+ private Date currentTime;
+ private Set<ConnectorEntity> connectors;
+
+ @XmlJavaTypeAdapter(TimeAdapter.class)
+ @Schema(description = "The current time on the system.",
+ type = "string"
+ )
+ public Date getCurrentTime() {
+ return currentTime;
+ }
+
+ public void setCurrentTime(final Date currentTime) {
+ this.currentTime = currentTime;
+ }
+
+ public Set<ConnectorEntity> getConnectors() {
+ return connectors;
+ }
+
+ public void setConnectors(final Set<ConnectorEntity> connectors) {
+ this.connectors = connectors;
+ }
+}
+
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java
index 0756eaf66d..8bc5b65878 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java
@@ -40,6 +40,7 @@ public class CurrentUserEntity extends Entity {
private PermissionsDTO policiesPermissions;
private PermissionsDTO systemPermissions;
private PermissionsDTO parameterContextPermissions;
+ private PermissionsDTO connectorsPermissions;
private PermissionsDTO restrictedComponentsPermissions;
private Set<ComponentRestrictionPermissionDTO> componentRestrictionPermissions;
@@ -165,6 +166,18 @@ public class CurrentUserEntity extends Entity {
this.parameterContextPermissions = parameterContextPermissions;
}
+ /**
+ * @return permissions for accessing connectors
+ */
+ @Schema(description = "Permissions for accessing connectors.")
+ public PermissionsDTO getConnectorsPermissions() {
+ return connectorsPermissions;
+ }
+
+ public void setConnectorsPermissions(PermissionsDTO connectorsPermissions) {
+ this.connectorsPermissions = connectorsPermissions;
+ }
+
/**
* @return permissions for accessing the restricted components
*/
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyConnectorConfigStepRequestEntity.java
similarity index 54%
copy from nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
copy to nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyConnectorConfigStepRequestEntity.java
index 17c2d91e2d..cacfe93c33 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyConnectorConfigStepRequestEntity.java
@@ -14,21 +14,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.web;
+package org.apache.nifi.web.api.entity;
-/**
- * Types of UI extensions. Since a UI extension could support multiple types of
- * custom UIs it will need to include the type so the framework can appropriate
- * understand and process the request (recording actions in the audit database,
- * replicating a request throughout the cluster to the appropriate endpoints,
- * etc).
- */
-public enum UiExtensionType {
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import org.apache.nifi.web.api.dto.VerifyConnectorConfigStepRequestDTO;
+
+@XmlRootElement(name = "verifyConnectorConfigStepRequestEntity")
+public class VerifyConnectorConfigStepRequestEntity extends Entity {
+
+ private VerifyConnectorConfigStepRequestDTO request;
- ContentViewer,
- ProcessorConfiguration,
- ControllerServiceConfiguration,
- ReportingTaskConfiguration,
- ParameterProviderConfiguration,
- FlowRegistryClientConfiguration,
+ @Schema(description = "The verification request")
+ public VerifyConnectorConfigStepRequestDTO getRequest() {
+ return request;
+ }
+
+ public void setRequest(final VerifyConnectorConfigStepRequestDTO request) {
+ this.request = request;
+ }
}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
index c5635968eb..651b51f89d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
@@ -298,6 +298,24 @@ public final class ResourceFactory {
}
};
+ private final static Resource CONNECTORS_RESOURCE = new Resource() {
+
+ @Override
+ public String getIdentifier() {
+ return ResourceType.Connector.getValue();
+ }
+
+ @Override
+ public String getName() {
+ return "Connectors";
+ }
+
+ @Override
+ public String getSafeDescription() {
+ return "connectors";
+ }
+ };
+
/**
* Gets the Resource for accessing the Controller. This includes Controller level configuration, bulletins, reporting tasks, and the cluster.
@@ -342,6 +360,14 @@ public final class ResourceFactory {
return PARAMETER_PROVIDERS_RESOURCE;
}
+ /**
+ * Gets the Resource for accessing Connectors.
+ * @return The resource for access Connectors
+ */
+ public static Resource getConnectorsResource() {
+ return CONNECTORS_RESOURCE;
+ }
+
/**
* Gets the Resource for accessing provenance. Access to this Resource allows the user to access data provenance. However, additional authorization
* is required based on the component that generated the event and the attributes of the event.
@@ -560,6 +586,7 @@ public final class ResourceFactory {
case Label -> "Label";
case ParameterContext -> "Parameter Context";
case ParameterProvider -> "Parameter Provider";
+ case Connector -> "Connector";
default -> "Component";
};
return componentType + " with ID " + identifier;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
index f16d4eedc2..daafc89fbc 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
@@ -24,6 +24,11 @@ import org.apache.nifi.cluster.coordination.http.endpoints.ClearBulletinsEndpoin
import org.apache.nifi.cluster.coordination.http.endpoints.ClearBulletinsForGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ComponentStateEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorFlowEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorPropertyGroupEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorPropertyGroupNamesEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionStatusEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ControllerBulletinsEndpointMerger;
@@ -98,6 +103,7 @@ import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupEndpointMerg
import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.UsersEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.VerifyConfigEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.VerifyConnectorConfigStepEndpointMerger;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.stream.io.NullOutputStream;
import org.apache.nifi.util.FormatUtils;
@@ -138,6 +144,12 @@ public class StandardHttpResponseMapper implements HttpResponseMapper {
endpointMergers.add(new ProcessorEndpointMerger());
endpointMergers.add(new ProcessorsEndpointMerger());
endpointMergers.add(new ProcessorRunStatusDetailsEndpointMerger());
+ endpointMergers.add(new ConnectorEndpointMerger());
+ endpointMergers.add(new ConnectorsEndpointMerger());
+ endpointMergers.add(new ConnectorFlowEndpointMerger());
+ endpointMergers.add(new ConnectorPropertyGroupEndpointMerger());
+ endpointMergers.add(new ConnectorPropertyGroupNamesEndpointMerger());
+ endpointMergers.add(new VerifyConnectorConfigStepEndpointMerger());
endpointMergers.add(new ConnectionEndpointMerger());
endpointMergers.add(new ConnectionsEndpointMerger());
endpointMergers.add(new PortEndpointMerger());
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorEndpointMerger.java
new file mode 100644
index 0000000000..a2481698e1
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorEndpointMerger.java
@@ -0,0 +1,60 @@
+/*
+ * 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.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.ConnectorEntityMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class ConnectorEndpointMerger extends AbstractSingleEntityEndpoint<ConnectorEntity> implements EndpointResponseMerger {
+ public static final Pattern CONNECTORS_URI_PATTERN = Pattern.compile("/nifi-api/connectors");
+ public static final Pattern CONNECTOR_URI_PATTERN = Pattern.compile("/nifi-api/connectors/[a-f0-9\\-]{36}");
+ public static final Pattern CONNECTOR_RUN_STATUS_URI_PATTERN = Pattern.compile("/nifi-api/connectors/[a-f0-9\\-]{36}/run-status");
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (CONNECTOR_URI_PATTERN.matcher(uri.getPath()).matches())) {
+ return true;
+ } else if ("PUT".equalsIgnoreCase(method) && CONNECTOR_RUN_STATUS_URI_PATTERN.matcher(uri.getPath()).matches()) {
+ return true;
+ } else if ("POST".equalsIgnoreCase(method) && CONNECTORS_URI_PATTERN.matcher(uri.getPath()).matches()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected Class<ConnectorEntity> getEntityClass() {
+ return ConnectorEntity.class;
+ }
+
+ @Override
+ protected void mergeResponses(final ConnectorEntity clientEntity, final Map<NodeIdentifier, ConnectorEntity> entityMap, final Set<NodeResponse> successfulResponses,
+ final Set<NodeResponse> problematicResponses) {
+
+ ConnectorEntityMerger.merge(clientEntity, entityMap);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorFlowEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorFlowEndpointMerger.java
new file mode 100644
index 0000000000..a933ac4696
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorFlowEndpointMerger.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class ConnectorFlowEndpointMerger extends AbstractSingleDTOEndpoint<ProcessGroupFlowEntity, ProcessGroupFlowDTO> {
+ public static final Pattern CONNECTOR_FLOW_URI_PATTERN = Pattern.compile("/nifi-api/connectors/[a-f0-9\\-]{36}/flow");
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return "GET".equalsIgnoreCase(method) && CONNECTOR_FLOW_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ protected Class<ProcessGroupFlowEntity> getEntityClass() {
+ return ProcessGroupFlowEntity.class;
+ }
+
+ @Override
+ protected ProcessGroupFlowDTO getDto(final ProcessGroupFlowEntity entity) {
+ return entity.getProcessGroupFlow();
+ }
+
+ @Override
+ protected void mergeResponses(final ProcessGroupFlowDTO clientDto, final Map<NodeIdentifier, ProcessGroupFlowDTO> dtoMap,
+ final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+
+ // Delegate to the shared flow merging logic
+ FlowMerger.mergeProcessGroupFlowDto(clientDto, dtoMap);
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupEndpointMerger.java
new file mode 100644
index 0000000000..46a94ae00f
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupEndpointMerger.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.ConfigurationStepEntityMerger;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class ConnectorPropertyGroupEndpointMerger extends AbstractSingleEntityEndpoint<ConfigurationStepEntity> implements EndpointResponseMerger {
+ public static final Pattern CONNECTOR_PROPERTY_GROUP_URI_PATTERN = Pattern.compile("/nifi-api/connectors/[a-f0-9\\-]{36}/configuration-steps/[^/]+");
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return ("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method))
+ && CONNECTOR_PROPERTY_GROUP_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ protected Class<ConfigurationStepEntity> getEntityClass() {
+ return ConfigurationStepEntity.class;
+ }
+
+ @Override
+ protected void mergeResponses(final ConfigurationStepEntity clientEntity, final Map<NodeIdentifier, ConfigurationStepEntity> entityMap,
+ final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+
+ ConfigurationStepEntityMerger.merge(clientEntity, entityMap);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupNamesEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupNamesEndpointMerger.java
new file mode 100644
index 0000000000..7fd64886a0
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorPropertyGroupNamesEndpointMerger.java
@@ -0,0 +1,63 @@
+/*
+ * 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.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.ConfigurationStepNamesEntityMerger;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class ConnectorPropertyGroupNamesEndpointMerger implements EndpointResponseMerger {
+ public static final Pattern CONNECTOR_PROPERTY_GROUPS_URI_PATTERN = Pattern.compile("/nifi-api/connectors/[a-f0-9\\-]{36}/configuration-steps");
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return "GET".equalsIgnoreCase(method) && CONNECTOR_PROPERTY_GROUPS_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ public final NodeResponse merge(final URI uri, final String method, final Set<NodeResponse> successfulResponses,
+ final Set<NodeResponse> problematicResponses, final NodeResponse clientResponse) {
+ if (!canHandle(uri, method)) {
+ throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName()
+ + " to map responses for URI " + uri + ", HTTP Method " + method);
+ }
+
+ final ConfigurationStepNamesEntity responseEntity = clientResponse.getClientResponse().readEntity(ConfigurationStepNamesEntity.class);
+
+ final Map<NodeIdentifier, ConfigurationStepNamesEntity> entityMap = new HashMap<>();
+ for (final NodeResponse nodeResponse : successfulResponses) {
+ final ConfigurationStepNamesEntity nodeResponseEntity = nodeResponse == clientResponse
+ ? responseEntity
+ : nodeResponse.getClientResponse().readEntity(ConfigurationStepNamesEntity.class);
+ entityMap.put(nodeResponse.getNodeId(), nodeResponseEntity);
+ }
+
+ ConfigurationStepNamesEntityMerger.merge(responseEntity, entityMap);
+
+ // create a new client response
+ return new NodeResponse(clientResponse, responseEntity);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorsEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorsEndpointMerger.java
new file mode 100644
index 0000000000..9d89fb3b2d
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConnectorsEndpointMerger.java
@@ -0,0 +1,66 @@
+/*
+ * 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.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.ConnectorsEntityMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorsEntity;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class ConnectorsEndpointMerger implements EndpointResponseMerger {
+ public static final Pattern CONNECTORS_URI_PATTERN = Pattern.compile("/nifi-api/connectors");
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return "GET".equalsIgnoreCase(method) && CONNECTORS_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ public final NodeResponse merge(final URI uri, final String method, final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses, final NodeResponse clientResponse) {
+ if (!canHandle(uri, method)) {
+ throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method);
+ }
+
+ final ConnectorsEntity responseEntity = clientResponse.getClientResponse().readEntity(ConnectorsEntity.class);
+ final Set<ConnectorEntity> connectorEntities = responseEntity.getConnectors();
+
+ final Map<String, Map<NodeIdentifier, ConnectorEntity>> entityMap = new HashMap<>();
+ for (final NodeResponse nodeResponse : successfulResponses) {
+ final ConnectorsEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().readEntity(ConnectorsEntity.class);
+ final Set<ConnectorEntity> nodeConnectorEntities = nodeResponseEntity.getConnectors();
+
+ for (final ConnectorEntity nodeConnectorEntity : nodeConnectorEntities) {
+ Map<NodeIdentifier, ConnectorEntity> innerMap = entityMap.computeIfAbsent(nodeConnectorEntity.getId(), k -> new HashMap<>());
+ innerMap.put(nodeResponse.getNodeId(), nodeConnectorEntity);
+ }
+ }
+
+ ConnectorsEntityMerger.mergeConnectors(connectorEntities, entityMap);
+
+ // create a new client response
+ return new NodeResponse(clientResponse, responseEntity);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMerger.java
index 03691c4bc7..fd7bc17374 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMerger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMerger.java
@@ -56,6 +56,7 @@ public class CurrentUserEndpointMerger extends AbstractSingleEntityEndpoint<Curr
mergePermissions(clientEntity.getTenantsPermissions(), entity.getTenantsPermissions());
mergePermissions(clientEntity.getSystemPermissions(), entity.getSystemPermissions());
mergePermissions(clientEntity.getTenantsPermissions(), entity.getTenantsPermissions());
+ mergePermissions(clientEntity.getConnectorsPermissions(), entity.getConnectorsPermissions());
final Set<ComponentRestrictionPermissionDTO> clientEntityComponentRestrictionsPermissions = clientEntity.getComponentRestrictionPermissions();
final Set<ComponentRestrictionPermissionDTO> entityComponentRestrictionsPermissions = entity.getComponentRestrictionPermissions();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/FlowMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/FlowMerger.java
index ad15fa1269..cd84ddcdce 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/FlowMerger.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/FlowMerger.java
@@ -65,6 +65,17 @@ public class FlowMerger extends AbstractSingleDTOEndpoint<ProcessGroupFlowEntity
protected void mergeResponses(final ProcessGroupFlowDTO clientDto, final Map<NodeIdentifier, ProcessGroupFlowDTO> dtoMap,
final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+ mergeProcessGroupFlowDto(clientDto, dtoMap);
+ }
+
+ /**
+ * Merges the ProcessGroupFlowDTO responses from all nodes into the client DTO.
+ * This method is package-private to allow reuse by other endpoint mergers (e.g., ConnectorFlowEndpointMerger).
+ *
+ * @param clientDto the client DTO to merge responses into
+ * @param dtoMap the responses from all nodes
+ */
+ static void mergeProcessGroupFlowDto(final ProcessGroupFlowDTO clientDto, final Map<NodeIdentifier, ProcessGroupFlowDTO> dtoMap) {
final FlowDTO flowDto = clientDto.getFlow();
final Set<ConnectionEntity> clientConnections = flowDto.getConnections();
final Set<ProcessorEntity> clientProcessors = flowDto.getProcessors();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyConnectorConfigStepEndpointMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyConnectorConfigStepEndpointMerger.java
new file mode 100644
index 0000000000..9f32b50561
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyConnectorConfigStepEndpointMerger.java
@@ -0,0 +1,72 @@
+/*
+ * 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.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.VerifyConnectorConfigStepRequestDTO;
+import org.apache.nifi.web.api.entity.VerifyConnectorConfigStepRequestEntity;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class VerifyConnectorConfigStepEndpointMerger extends AbstractSingleEntityEndpoint<VerifyConnectorConfigStepRequestEntity> {
+ public static final Pattern VERIFY_CONNECTOR_CONFIG_STEP_URI_PATTERN =
+ Pattern.compile("/nifi-api/connectors/[a-f0-9\\-]{36}/configuration-steps/[^/]+/verify-config(/[a-f0-9\\-]{36})?");
+
+ @Override
+ protected Class<VerifyConnectorConfigStepRequestEntity> getEntityClass() {
+ return VerifyConnectorConfigStepRequestEntity.class;
+ }
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return VERIFY_CONNECTOR_CONFIG_STEP_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ protected void mergeResponses(final VerifyConnectorConfigStepRequestEntity clientEntity,
+ final Map<NodeIdentifier, VerifyConnectorConfigStepRequestEntity> entityMap,
+ final Set<NodeResponse> successfulResponses,
+ final Set<NodeResponse> problematicResponses) {
+
+ final VerifyConnectorConfigStepRequestDTO requestDto = clientEntity.getRequest();
+ final List<ConfigVerificationResultDTO> results = requestDto.getResults();
+
+ if (results == null) {
+ return;
+ }
+
+ final ConfigVerificationResultMerger resultMerger = new ConfigVerificationResultMerger();
+ for (final Map.Entry<NodeIdentifier, VerifyConnectorConfigStepRequestEntity> entry : entityMap.entrySet()) {
+ final NodeIdentifier nodeId = entry.getKey();
+ final VerifyConnectorConfigStepRequestEntity entity = entry.getValue();
+
+ final List<ConfigVerificationResultDTO> nodeResults = entity.getRequest().getResults();
+ resultMerger.addNodeResults(nodeId, nodeResults);
+ }
+
+ final List<ConfigVerificationResultDTO> aggregateResults = resultMerger.computeAggregateResults();
+
+ clientEntity.getRequest().setResults(aggregateResults);
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
index 68c4212cef..49a6537007 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
@@ -37,6 +37,7 @@ import org.apache.nifi.cluster.coordination.http.endpoints.ParameterProviderEndp
import org.apache.nifi.cluster.coordination.http.endpoints.PortEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ProcessGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ProcessorEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.ConnectorEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.RemoteProcessGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskEndpointMerger;
import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
@@ -674,6 +675,7 @@ public class ThreadPoolRequestReplicator implements RequestReplicator, Closeable
// Processors, which is done by issuing a request to DELETE /processors/<id>/threads
return ConnectionEndpointMerger.CONNECTION_URI_PATTERN.matcher(uriPath).matches()
|| ProcessorEndpointMerger.PROCESSOR_URI_PATTERN.matcher(uriPath).matches()
+ || ConnectorEndpointMerger.CONNECTOR_URI_PATTERN.matcher(uriPath).matches()
|| FunnelEndpointMerger.FUNNEL_URI_PATTERN.matcher(uriPath).matches()
|| PortEndpointMerger.INPUT_PORT_URI_PATTERN.matcher(uriPath).matches()
|| PortEndpointMerger.OUTPUT_PORT_URI_PATTERN.matcher(uriPath).matches()
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConfigurationStepEntityMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConfigurationStepEntityMerger.java
new file mode 100644
index 0000000000..4ac0f52e3c
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConfigurationStepEntityMerger.java
@@ -0,0 +1,160 @@
+/*
+ * 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.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorPropertyDescriptorDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ConfigurationStepEntityMerger {
+
+ /**
+ * Merges the ConfigurationStepEntity responses.
+ * Note: Permissions are not merged here since they are managed at the parent Connector level.
+ *
+ * @param clientEntity the entity being returned to the client
+ * @param entityMap all node responses
+ */
+ public static void merge(final ConfigurationStepEntity clientEntity, final Map<NodeIdentifier, ConfigurationStepEntity> entityMap) {
+ final ConfigurationStepConfigurationDTO clientDto = clientEntity.getConfigurationStep();
+ final Map<NodeIdentifier, ConfigurationStepConfigurationDTO> dtoMap = new HashMap<>();
+
+ for (final Map.Entry<NodeIdentifier, ConfigurationStepEntity> entry : entityMap.entrySet()) {
+ final ConfigurationStepEntity nodeConfigurationStepEntity = entry.getValue();
+ final ConfigurationStepConfigurationDTO nodeConfigurationStepDto = nodeConfigurationStepEntity.getConfigurationStep();
+ dtoMap.put(entry.getKey(), nodeConfigurationStepDto);
+ }
+
+ mergeDtos(clientDto, dtoMap);
+ }
+
+ private static void mergeDtos(final ConfigurationStepConfigurationDTO clientDto, final Map<NodeIdentifier, ConfigurationStepConfigurationDTO> dtoMap) {
+ // if unauthorized for the client dto, simply return
+ if (clientDto == null) {
+ return;
+ }
+
+ // Configuration step configurations should be consistent across all nodes
+ // We primarily need to ensure that the configuration step name is consistent
+ // The property values within property groups should also be consistent across nodes
+
+ for (final Map.Entry<NodeIdentifier, ConfigurationStepConfigurationDTO> nodeEntry : dtoMap.entrySet()) {
+ final ConfigurationStepConfigurationDTO nodeConfigurationStep = nodeEntry.getValue();
+
+ if (nodeConfigurationStep != null) {
+ // Verify that configuration step names are consistent
+ if (clientDto.getConfigurationStepName() != null && nodeConfigurationStep.getConfigurationStepName() != null) {
+ if (!clientDto.getConfigurationStepName().equals(nodeConfigurationStep.getConfigurationStepName())) {
+ // Log inconsistency but use the client's version
+ // In a properly functioning cluster, configuration step names should be consistent
+ }
+ }
+
+ // Merge property group configurations
+ mergePropertyGroupConfigurations(clientDto.getPropertyGroupConfigurations(), nodeConfigurationStep.getPropertyGroupConfigurations());
+ }
+ }
+
+ // Merge property descriptors to handle dynamic allowable values
+ mergePropertyDescriptors(clientDto, dtoMap);
+ }
+
+ private static void mergePropertyGroupConfigurations(final List<PropertyGroupConfigurationDTO> clientPropertyGroups, final List<PropertyGroupConfigurationDTO> nodePropertyGroups) {
+ if (clientPropertyGroups == null || nodePropertyGroups == null) {
+ return;
+ }
+
+ // For property groups, we expect configurations to be consistent across nodes
+ // This merger primarily ensures that we have a consistent view
+ // Property values should not differ between nodes in a properly configured cluster
+
+ for (final PropertyGroupConfigurationDTO clientPropertyGroup : clientPropertyGroups) {
+ for (final PropertyGroupConfigurationDTO nodePropertyGroup : nodePropertyGroups) {
+ if (clientPropertyGroup.getPropertyGroupName() != null
+ && clientPropertyGroup.getPropertyGroupName().equals(nodePropertyGroup.getPropertyGroupName())) {
+
+ // Property values should be consistent across nodes
+ // In case of any inconsistency, we use the client's values
+ // This is primarily for defensive programming
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Merges property descriptors across all nodes in the configuration step.
+ * This is necessary to handle dynamic allowable values that may differ between nodes.
+ *
+ * @param clientDto the configuration step DTO being returned to the client
+ * @param dtoMap all node configuration step DTOs
+ */
+ static void mergePropertyDescriptors(final ConfigurationStepConfigurationDTO clientDto, final Map<NodeIdentifier, ConfigurationStepConfigurationDTO> dtoMap) {
+ if (clientDto == null || clientDto.getPropertyGroupConfigurations() == null) {
+ return;
+ }
+
+ for (final PropertyGroupConfigurationDTO clientPropertyGroup : clientDto.getPropertyGroupConfigurations()) {
+ if (clientPropertyGroup.getPropertyDescriptors() == null) {
+ continue;
+ }
+
+ // Build a map of property descriptors from all nodes for this property group
+ final Map<String, Map<NodeIdentifier, ConnectorPropertyDescriptorDTO>> propertyDescriptorMap = new HashMap<>();
+
+ for (final Map.Entry<NodeIdentifier, ConfigurationStepConfigurationDTO> nodeEntry : dtoMap.entrySet()) {
+ final ConfigurationStepConfigurationDTO nodeConfigStep = nodeEntry.getValue();
+ if (nodeConfigStep == null || nodeConfigStep.getPropertyGroupConfigurations() == null) {
+ continue;
+ }
+
+ // Find the matching property group in the node's configuration
+ for (final PropertyGroupConfigurationDTO nodePropertyGroup : nodeConfigStep.getPropertyGroupConfigurations()) {
+ if (clientPropertyGroup.getPropertyGroupName() != null
+ && clientPropertyGroup.getPropertyGroupName().equals(nodePropertyGroup.getPropertyGroupName())
+ && nodePropertyGroup.getPropertyDescriptors() != null) {
+
+ // Aggregate property descriptors by name
+ for (final ConnectorPropertyDescriptorDTO propertyDescriptor : nodePropertyGroup.getPropertyDescriptors().values()) {
+ propertyDescriptorMap.computeIfAbsent(propertyDescriptor.getName(), name -> new HashMap<>())
+ .put(nodeEntry.getKey(), propertyDescriptor);
+ }
+ }
+ }
+ }
+
+ // Merge each property descriptor
+ for (final Map.Entry<String, Map<NodeIdentifier, ConnectorPropertyDescriptorDTO>> entry : propertyDescriptorMap.entrySet()) {
+ final String propertyName = entry.getKey();
+ final Map<NodeIdentifier, ConnectorPropertyDescriptorDTO> descriptorByNodeId = entry.getValue();
+
+ // Get the client's property descriptor directly from the map
+ final ConnectorPropertyDescriptorDTO clientPropertyDescriptor = clientPropertyGroup.getPropertyDescriptors().get(propertyName);
+
+ if (clientPropertyDescriptor != null) {
+ ConnectorPropertyDescriptorDtoMerger.merge(clientPropertyDescriptor, descriptorByNodeId);
+ }
+ }
+ }
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConfigurationStepNamesEntityMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConfigurationStepNamesEntityMerger.java
new file mode 100644
index 0000000000..66e541173d
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConfigurationStepNamesEntityMerger.java
@@ -0,0 +1,62 @@
+/*
+ * 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.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ConfigurationStepNamesEntityMerger {
+
+ /**
+ * Merges multiple ConfigurationStepNamesEntity responses.
+ *
+ * @param clientEntity the entity being returned to the client
+ * @param entityMap all node responses
+ */
+ public static void merge(final ConfigurationStepNamesEntity clientEntity, final Map<NodeIdentifier, ConfigurationStepNamesEntity> entityMap) {
+ if (clientEntity == null) {
+ return;
+ }
+
+ final Set<String> allConfigurationStepNames = new HashSet<>();
+
+ // Add client's configuration step names
+ if (clientEntity.getConfigurationStepNames() != null) {
+ allConfigurationStepNames.addAll(clientEntity.getConfigurationStepNames());
+ }
+
+ // Merge configuration step names from all nodes
+ for (final Map.Entry<NodeIdentifier, ConfigurationStepNamesEntity> entry : entityMap.entrySet()) {
+ final ConfigurationStepNamesEntity nodeEntity = entry.getValue();
+
+ if (nodeEntity != null && nodeEntity.getConfigurationStepNames() != null) {
+ allConfigurationStepNames.addAll(nodeEntity.getConfigurationStepNames());
+ }
+ }
+
+ // Set the merged configuration step names (sorted for consistency)
+ final List<String> sortedConfigurationStepNames = new ArrayList<>(allConfigurationStepNames);
+ sortedConfigurationStepNames.sort(String::compareTo);
+ clientEntity.setConfigurationStepNames(sortedConfigurationStepNames);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java
new file mode 100644
index 0000000000..9b26fe3629
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorEntityMerger.java
@@ -0,0 +1,128 @@
+/*
+ * 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.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ConnectorEntityMerger {
+
+ /**
+ * Merges the ConnectorEntity responses.
+ *
+ * @param clientEntity the entity being returned to the client
+ * @param entityMap all node responses
+ */
+ public static void merge(final ConnectorEntity clientEntity, final Map<NodeIdentifier, ConnectorEntity> entityMap) {
+ final ConnectorDTO clientDto = clientEntity.getComponent();
+ final Map<NodeIdentifier, ConnectorDTO> dtoMap = new HashMap<>();
+ for (final Map.Entry<NodeIdentifier, ConnectorEntity> entry : entityMap.entrySet()) {
+ final ConnectorEntity nodeConnectorEntity = entry.getValue();
+ final ConnectorDTO nodeConnectorDto = nodeConnectorEntity.getComponent();
+ dtoMap.put(entry.getKey(), nodeConnectorDto);
+ }
+
+ mergeDtos(clientDto, dtoMap);
+ }
+
+ private static void mergeDtos(final ConnectorDTO clientDto, final Map<NodeIdentifier, ConnectorDTO> dtoMap) {
+ // if unauthorized for the client dto, simple return
+ if (clientDto == null) {
+ return;
+ }
+
+ // Basic connector state consistency check - all nodes should have same state
+ for (final Map.Entry<NodeIdentifier, ConnectorDTO> nodeEntry : dtoMap.entrySet()) {
+ final ConnectorDTO nodeConnector = nodeEntry.getValue();
+
+ if (nodeConnector != null) {
+ // Ensure state consistency across cluster
+ if (clientDto.getState() == null && nodeConnector.getState() != null) {
+ clientDto.setState(nodeConnector.getState());
+ }
+ }
+ }
+
+ // Merge configuration steps to handle dynamic property descriptors
+ mergeActiveConfiguration(clientDto, dtoMap);
+ mergeWorkingConfiguration(clientDto, dtoMap);
+ }
+
+ private static void mergeActiveConfiguration(final ConnectorDTO clientDto, final Map<NodeIdentifier, ConnectorDTO> dtoMap) {
+ final ConnectorConfigurationDTO clientConfig = clientDto.getActiveConfiguration();
+ if (clientConfig == null || clientConfig.getConfigurationStepConfigurations() == null) {
+ return;
+ }
+
+ // For each configuration step in the client's active configuration, merge it with the corresponding steps from all nodes
+ for (final ConfigurationStepConfigurationDTO clientStep : clientConfig.getConfigurationStepConfigurations()) {
+ final Map<NodeIdentifier, ConfigurationStepConfigurationDTO> stepDtoMap = new HashMap<>();
+
+ for (final Map.Entry<NodeIdentifier, ConnectorDTO> nodeEntry : dtoMap.entrySet()) {
+ final ConnectorDTO nodeDto = nodeEntry.getValue();
+ if (nodeDto == null || nodeDto.getActiveConfiguration() == null || nodeDto.getActiveConfiguration().getConfigurationStepConfigurations() == null) {
+ continue;
+ }
+
+ // Find the matching configuration step in the node's active configuration
+ nodeDto.getActiveConfiguration().getConfigurationStepConfigurations().stream()
+ .filter(step -> clientStep.getConfigurationStepName() != null
+ && clientStep.getConfigurationStepName().equals(step.getConfigurationStepName()))
+ .findFirst()
+ .ifPresent(step -> stepDtoMap.put(nodeEntry.getKey(), step));
+ }
+
+ // Merge property descriptors for this configuration step
+ ConfigurationStepEntityMerger.mergePropertyDescriptors(clientStep, stepDtoMap);
+ }
+ }
+
+ private static void mergeWorkingConfiguration(final ConnectorDTO clientDto, final Map<NodeIdentifier, ConnectorDTO> dtoMap) {
+ final ConnectorConfigurationDTO clientConfig = clientDto.getWorkingConfiguration();
+ if (clientConfig == null || clientConfig.getConfigurationStepConfigurations() == null) {
+ return;
+ }
+
+ // For each configuration step in the client's working configuration, merge it with the corresponding steps from all nodes
+ for (final ConfigurationStepConfigurationDTO clientStep : clientConfig.getConfigurationStepConfigurations()) {
+ final Map<NodeIdentifier, ConfigurationStepConfigurationDTO> stepDtoMap = new HashMap<>();
+
+ for (final Map.Entry<NodeIdentifier, ConnectorDTO> nodeEntry : dtoMap.entrySet()) {
+ final ConnectorDTO nodeDto = nodeEntry.getValue();
+ if (nodeDto == null || nodeDto.getWorkingConfiguration() == null || nodeDto.getWorkingConfiguration().getConfigurationStepConfigurations() == null) {
+ continue;
+ }
+
+ // Find the matching configuration step in the node's working configuration
+ nodeDto.getWorkingConfiguration().getConfigurationStepConfigurations().stream()
+ .filter(step -> clientStep.getConfigurationStepName() != null
+ && clientStep.getConfigurationStepName().equals(step.getConfigurationStepName()))
+ .findFirst()
+ .ifPresent(step -> stepDtoMap.put(nodeEntry.getKey(), step));
+ }
+
+ // Merge property descriptors for this configuration step
+ ConfigurationStepEntityMerger.mergePropertyDescriptors(clientStep, stepDtoMap);
+ }
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorPropertyDescriptorDtoMerger.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorPropertyDescriptorDtoMerger.java
new file mode 100644
index 0000000000..ce6b29bffa
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorPropertyDescriptorDtoMerger.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.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.AllowableValueDTO;
+import org.apache.nifi.web.api.dto.ConnectorPropertyDescriptorDTO;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class ConnectorPropertyDescriptorDtoMerger {
+ public static void merge(final ConnectorPropertyDescriptorDTO clientPropertyDescriptor, final Map<NodeIdentifier, ConnectorPropertyDescriptorDTO> dtoMap) {
+ if (clientPropertyDescriptor.getAllowableValues() != null) {
+ final Map<AllowableValueDTO, List<AllowableValueEntity>> allowableValueDtoToEntities = new LinkedHashMap<>();
+
+ addEntities(clientPropertyDescriptor, allowableValueDtoToEntities);
+ dtoMap.values().forEach(propertyDescriptorDTO -> addEntities(propertyDescriptorDTO, allowableValueDtoToEntities));
+
+ // Consider each allowable value from the client property descriptor. In the event of a duplicate allowable value, each entry will still
+ // be included in the merged list. Also ensure that each allowable value is present in all nodes property descriptors.
+ final List<AllowableValueEntity> mergedAllowableValues = clientPropertyDescriptor.getAllowableValues().stream()
+ .map(allowableValueEntity -> allowableValueDtoToEntities.getOrDefault(allowableValueEntity.getAllowableValue(), Collections.emptyList()))
+ .filter(entities -> !entities.isEmpty() && allNodesHaveAllowableValue(entities.getFirst(), dtoMap))
+ .map(AllowableValueEntityMerger::merge)
+ .collect(Collectors.toList());
+
+ clientPropertyDescriptor.setAllowableValues(mergedAllowableValues);
+ }
+ }
+
+ private static boolean allNodesHaveAllowableValue(final AllowableValueEntity entity, final Map<NodeIdentifier, ConnectorPropertyDescriptorDTO> dtoMap) {
+ return dtoMap.values().stream()
+ .allMatch(propertyDescriptorDTO ->
+ propertyDescriptorDTO.getAllowableValues() != null && propertyDescriptorDTO.getAllowableValues().stream()
+ .anyMatch(allowableValueEntity -> allowableValueEntity.getAllowableValue().equals(entity.getAllowableValue())));
+ }
+
+ private static void addEntities(final ConnectorPropertyDescriptorDTO propertyDescriptorDTO, final Map<AllowableValueDTO, List<AllowableValueEntity>> dtoToEntities) {
+ propertyDescriptorDTO.getAllowableValues().forEach(
+ allowableValueEntity -> dtoToEntities.computeIfAbsent(allowableValueEntity.getAllowableValue(), __ -> new ArrayList<>()).add(allowableValueEntity)
+ );
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorsEntityMerger.java
similarity index 50%
copy from nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java
copy to nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorsEntityMerger.java
index da921b4535..a4d2e76925 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ConnectorsEntityMerger.java
@@ -14,35 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.ui.extension;
+package org.apache.nifi.cluster.manager;
-import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
-/**
- * Information about a UI extension required to be invoked.
- */
-public class UiExtension {
+import java.util.Map;
+import java.util.Set;
- private final UiExtensionType extensionType;
- private final String contextPath;
+public class ConnectorsEntityMerger {
- public UiExtension(final UiExtensionType extensionType, final String contextPath) {
- this.extensionType = extensionType;
- this.contextPath = contextPath;
- }
+ private static final ConnectorEntityMerger connectorEntityMerger = new ConnectorEntityMerger();
/**
- * @return type of this UI extension
+ * Merges multiple ConnectorEntity responses.
+ *
+ * @param connectorEntities entities being returned to the client
+ * @param entityMap all node responses
*/
- public UiExtensionType getExtensionType() {
- return extensionType;
+ public static void mergeConnectors(final Set<ConnectorEntity> connectorEntities, final Map<String, Map<NodeIdentifier, ConnectorEntity>> entityMap) {
+ for (final ConnectorEntity entity : connectorEntities) {
+ connectorEntityMerger.merge(entity, entityMap.get(entity.getId()));
+ }
}
-
- /**
- * @return The context path of this UI extension
- */
- public String getContextPath() {
- return contextPath;
- }
-
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMergerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMergerTest.java
index 48d3ce0a51..2fdaff121b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMergerTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/CurrentUserEndpointMergerTest.java
@@ -48,6 +48,7 @@ public class CurrentUserEndpointMergerTest {
userNode1.setRestrictedComponentsPermissions(buildPermissions(false, false));
userNode1.setSystemPermissions(buildPermissions(true, true));
userNode1.setTenantsPermissions(buildPermissions(false, true));
+ userNode1.setConnectorsPermissions(buildPermissions(true, false));
final Set<ComponentRestrictionPermissionDTO> componentRestrictionsNode1 = new HashSet<>();
componentRestrictionsNode1.add(buildComponentRestriction(RequiredPermission.ACCESS_KEYTAB, true, true));
@@ -64,6 +65,7 @@ public class CurrentUserEndpointMergerTest {
userNode2.setRestrictedComponentsPermissions(buildPermissions(true, true));
userNode2.setSystemPermissions(buildPermissions(false, false));
userNode2.setTenantsPermissions(buildPermissions(true, true));
+ userNode2.setConnectorsPermissions(buildPermissions(false, true));
final Set<ComponentRestrictionPermissionDTO> componentRestrictionsNode2 = new HashSet<>();
componentRestrictionsNode2.add(buildComponentRestriction(RequiredPermission.ACCESS_KEYTAB, true, false));
@@ -92,6 +94,8 @@ public class CurrentUserEndpointMergerTest {
assertFalse(userNode1.getSystemPermissions().getCanWrite());
assertFalse(userNode1.getTenantsPermissions().getCanRead());
assertTrue(userNode1.getTenantsPermissions().getCanWrite());
+ assertFalse(userNode1.getConnectorsPermissions().getCanRead());
+ assertFalse(userNode1.getConnectorsPermissions().getCanWrite());
userNode1.getComponentRestrictionPermissions().forEach(componentRestriction -> {
if (RequiredPermission.ACCESS_KEYTAB.getPermissionIdentifier().equals(componentRestriction.getRequiredPermission().getId())) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConfigurationStepEntityMergerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConfigurationStepEntityMergerTest.java
new file mode 100644
index 0000000000..d32e21bf74
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConfigurationStepEntityMergerTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.AllowableValueDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorPropertyDescriptorDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class ConfigurationStepEntityMergerTest {
+
+ @Test
+ void testMergePropertyDescriptorsAcrossNodes() {
+ final ConfigurationStepEntity clientEntity = createConfigurationStepEntity("step1",
+ Map.of("hostname", List.of("localhost", "server1", "server2")));
+
+ final ConfigurationStepEntity node1Entity = createConfigurationStepEntity("step1",
+ Map.of("hostname", List.of("localhost", "server1")));
+
+ final ConfigurationStepEntity node2Entity = createConfigurationStepEntity("step1",
+ Map.of("hostname", List.of("localhost", "server1", "server2")));
+
+ final Map<NodeIdentifier, ConfigurationStepEntity> entityMap = new HashMap<>();
+ entityMap.put(getNodeIdentifier("client", 8000), clientEntity);
+ entityMap.put(getNodeIdentifier("node1", 8001), node1Entity);
+ entityMap.put(getNodeIdentifier("node2", 8002), node2Entity);
+
+ ConfigurationStepEntityMerger.merge(clientEntity, entityMap);
+
+ // Should only include allowable values present on all nodes
+ final PropertyGroupConfigurationDTO propertyGroup = clientEntity.getConfigurationStep().getPropertyGroupConfigurations().get(0);
+ final ConnectorPropertyDescriptorDTO descriptor = propertyGroup.getPropertyDescriptors().get("hostname");
+
+ assertNotNull(descriptor.getAllowableValues());
+ assertEquals(2, descriptor.getAllowableValues().size());
+
+ final List<String> values = descriptor.getAllowableValues().stream()
+ .map(AllowableValueEntity::getAllowableValue)
+ .map(AllowableValueDTO::getValue)
+ .sorted()
+ .toList();
+
+ assertEquals(List.of("localhost", "server1"), values);
+ }
+
+ @Test
+ void testMergeWithNoOverlappingAllowableValues() {
+ final ConfigurationStepEntity clientEntity = createConfigurationStepEntity("step1",
+ Map.of("region", List.of("us-east-1", "us-west-1")));
+
+ final ConfigurationStepEntity node1Entity = createConfigurationStepEntity("step1",
+ Map.of("region", List.of("eu-west-1")));
+
+ final ConfigurationStepEntity node2Entity = createConfigurationStepEntity("step1",
+ Map.of("region", List.of("ap-south-1")));
+
+ final Map<NodeIdentifier, ConfigurationStepEntity> entityMap = new HashMap<>();
+ entityMap.put(getNodeIdentifier("client", 8000), clientEntity);
+ entityMap.put(getNodeIdentifier("node1", 8001), node1Entity);
+ entityMap.put(getNodeIdentifier("node2", 8002), node2Entity);
+
+ ConfigurationStepEntityMerger.merge(clientEntity, entityMap);
+
+ final PropertyGroupConfigurationDTO propertyGroup = clientEntity.getConfigurationStep().getPropertyGroupConfigurations().get(0);
+ final ConnectorPropertyDescriptorDTO descriptor = propertyGroup.getPropertyDescriptors().get("region");
+
+ assertNotNull(descriptor.getAllowableValues());
+ assertEquals(0, descriptor.getAllowableValues().size());
+ }
+
+ @Test
+ void testMergeMultiplePropertyGroups() {
+ final ConfigurationStepConfigurationDTO clientStep = new ConfigurationStepConfigurationDTO();
+ clientStep.setConfigurationStepName("database-config");
+
+ final PropertyGroupConfigurationDTO group1 = createPropertyGroup("connection",
+ Map.of("hostname", List.of("localhost", "server1")));
+ final PropertyGroupConfigurationDTO group2 = createPropertyGroup("credentials",
+ Map.of("auth-type", List.of("password", "certificate")));
+
+ clientStep.setPropertyGroupConfigurations(List.of(group1, group2));
+
+ final ConfigurationStepConfigurationDTO node1Step = new ConfigurationStepConfigurationDTO();
+ node1Step.setConfigurationStepName("database-config");
+ final PropertyGroupConfigurationDTO node1Group1 = createPropertyGroup("connection",
+ Map.of("hostname", List.of("localhost", "server1")));
+ final PropertyGroupConfigurationDTO node1Group2 = createPropertyGroup("credentials",
+ Map.of("auth-type", List.of("password")));
+ node1Step.setPropertyGroupConfigurations(List.of(node1Group1, node1Group2));
+
+ final Map<NodeIdentifier, ConfigurationStepConfigurationDTO> dtoMap = new HashMap<>();
+ dtoMap.put(getNodeIdentifier("client", 8000), clientStep);
+ dtoMap.put(getNodeIdentifier("node1", 8001), node1Step);
+
+ ConfigurationStepEntityMerger.mergePropertyDescriptors(clientStep, dtoMap);
+
+ // Verify first property group
+ final ConnectorPropertyDescriptorDTO hostnameDescriptor = group1.getPropertyDescriptors().get("hostname");
+ assertEquals(2, hostnameDescriptor.getAllowableValues().size());
+
+ // Verify second property group - should only have "password" since node1 doesn't have "certificate"
+ final ConnectorPropertyDescriptorDTO authDescriptor = group2.getPropertyDescriptors().get("auth-type");
+ assertEquals(1, authDescriptor.getAllowableValues().size());
+ assertEquals("password", authDescriptor.getAllowableValues().get(0).getAllowableValue().getValue());
+ }
+
+ private NodeIdentifier getNodeIdentifier(final String id, final int port) {
+ return new NodeIdentifier(id, "localhost", port, "localhost", port + 1, "localhost", port + 2, port + 3, true);
+ }
+
+ private ConfigurationStepEntity createConfigurationStepEntity(final String stepName, final Map<String, List<String>> propertyAllowableValues) {
+ final ConfigurationStepConfigurationDTO stepDto = new ConfigurationStepConfigurationDTO();
+ stepDto.setConfigurationStepName(stepName);
+ stepDto.setConfigurationStepDescription("Test configuration step");
+
+ final PropertyGroupConfigurationDTO propertyGroup = createPropertyGroup("default", propertyAllowableValues);
+ stepDto.setPropertyGroupConfigurations(List.of(propertyGroup));
+
+ final ConfigurationStepEntity entity = new ConfigurationStepEntity();
+ entity.setConfigurationStep(stepDto);
+
+ return entity;
+ }
+
+ private PropertyGroupConfigurationDTO createPropertyGroup(final String groupName, final Map<String, List<String>> propertyAllowableValues) {
+ final PropertyGroupConfigurationDTO propertyGroup = new PropertyGroupConfigurationDTO();
+ propertyGroup.setPropertyGroupName(groupName);
+
+ final Map<String, ConnectorPropertyDescriptorDTO> descriptors = new HashMap<>();
+ for (final Map.Entry<String, List<String>> entry : propertyAllowableValues.entrySet()) {
+ final ConnectorPropertyDescriptorDTO descriptor = new ConnectorPropertyDescriptorDTO();
+ descriptor.setName(entry.getKey());
+ descriptor.setDescription("Test property");
+ descriptor.setType("STRING");
+ descriptor.setRequired(false);
+
+ final List<AllowableValueEntity> allowableValues = entry.getValue().stream()
+ .map(value -> {
+ final AllowableValueDTO allowableValueDto = new AllowableValueDTO();
+ allowableValueDto.setValue(value);
+ allowableValueDto.setDisplayName(value);
+
+ final AllowableValueEntity entity = new AllowableValueEntity();
+ entity.setAllowableValue(allowableValueDto);
+ entity.setCanRead(true);
+ return entity;
+ })
+ .toList();
+
+ descriptor.setAllowableValues(allowableValues);
+ descriptors.put(entry.getKey(), descriptor);
+ }
+
+ propertyGroup.setPropertyDescriptors(descriptors);
+ propertyGroup.setPropertyValues(new HashMap<>());
+
+ return propertyGroup;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorEntityMergerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorEntityMergerTest.java
new file mode 100644
index 0000000000..da0ed151f2
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorEntityMergerTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.AllowableValueDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.dto.ConnectorPropertyDescriptorDTO;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class ConnectorEntityMergerTest {
+
+ @Test
+ void testMergeConnectorState() {
+ final ConnectorEntity clientEntity = createConnectorEntity("connector1", "STOPPED");
+ final ConnectorEntity node1Entity = createConnectorEntity("connector1", "STOPPED");
+ final ConnectorEntity node2Entity = createConnectorEntity("connector1", "STOPPED");
+
+ final Map<NodeIdentifier, ConnectorEntity> entityMap = new HashMap<>();
+ entityMap.put(getNodeIdentifier("client", 8000), clientEntity);
+ entityMap.put(getNodeIdentifier("node1", 8001), node1Entity);
+ entityMap.put(getNodeIdentifier("node2", 8002), node2Entity);
+
+ ConnectorEntityMerger.merge(clientEntity, entityMap);
+
+ assertEquals("STOPPED", clientEntity.getComponent().getState());
+ }
+
+ @Test
+ void testMergeConnectorStateWithNull() {
+ final ConnectorEntity clientEntity = createConnectorEntity("connector1", null);
+ final ConnectorEntity node1Entity = createConnectorEntity("connector1", "RUNNING");
+ final ConnectorEntity node2Entity = createConnectorEntity("connector1", "RUNNING");
+
+ final Map<NodeIdentifier, ConnectorEntity> entityMap = new HashMap<>();
+ entityMap.put(getNodeIdentifier("client", 8000), clientEntity);
+ entityMap.put(getNodeIdentifier("node1", 8001), node1Entity);
+ entityMap.put(getNodeIdentifier("node2", 8002), node2Entity);
+
+ ConnectorEntityMerger.merge(clientEntity, entityMap);
+
+ assertEquals("RUNNING", clientEntity.getComponent().getState());
+ }
+
+ @Test
+ void testMergeConfigurationStepsWithDifferentAllowableValues() {
+ final ConnectorEntity clientEntity = createConnectorEntityWithConfig(
+ "connector1", "STOPPED",
+ Map.of("hostname", List.of("localhost", "server1", "server2"))
+ );
+
+ final ConnectorEntity node1Entity = createConnectorEntityWithConfig(
+ "connector1", "STOPPED",
+ Map.of("hostname", List.of("localhost", "server1"))
+ );
+
+ final ConnectorEntity node2Entity = createConnectorEntityWithConfig(
+ "connector1", "STOPPED",
+ Map.of("hostname", List.of("localhost", "server1", "server2"))
+ );
+
+ final Map<NodeIdentifier, ConnectorEntity> entityMap = new HashMap<>();
+ entityMap.put(getNodeIdentifier("client", 8000), clientEntity);
+ entityMap.put(getNodeIdentifier("node1", 8001), node1Entity);
+ entityMap.put(getNodeIdentifier("node2", 8002), node2Entity);
+
+ ConnectorEntityMerger.merge(clientEntity, entityMap);
+
+ // Verify working configuration was merged - should only have values present on all nodes
+ final ConnectorConfigurationDTO config = clientEntity.getComponent().getWorkingConfiguration();
+ assertNotNull(config);
+
+ final ConfigurationStepConfigurationDTO step = config.getConfigurationStepConfigurations().get(0);
+ final PropertyGroupConfigurationDTO propertyGroup = step.getPropertyGroupConfigurations().get(0);
+ final ConnectorPropertyDescriptorDTO descriptor = propertyGroup.getPropertyDescriptors().get("hostname");
+
+ assertNotNull(descriptor.getAllowableValues());
+ assertEquals(2, descriptor.getAllowableValues().size());
+
+ final List<String> values = descriptor.getAllowableValues().stream()
+ .map(AllowableValueEntity::getAllowableValue)
+ .map(AllowableValueDTO::getValue)
+ .sorted()
+ .toList();
+
+ assertEquals(List.of("localhost", "server1"), values);
+ }
+
+ @Test
+ void testMergeMultipleConfigurationSteps() {
+ final ConnectorEntity clientEntity = createConnectorEntityWithMultipleSteps();
+ final ConnectorEntity node1Entity = createConnectorEntityWithMultipleSteps();
+
+ final Map<NodeIdentifier, ConnectorEntity> entityMap = new HashMap<>();
+ entityMap.put(getNodeIdentifier("client", 8000), clientEntity);
+ entityMap.put(getNodeIdentifier("node1", 8001), node1Entity);
+
+ ConnectorEntityMerger.merge(clientEntity, entityMap);
+
+ final ConnectorConfigurationDTO config = clientEntity.getComponent().getWorkingConfiguration();
+ assertNotNull(config);
+ assertEquals(2, config.getConfigurationStepConfigurations().size());
+ }
+
+ private NodeIdentifier getNodeIdentifier(final String id, final int port) {
+ return new NodeIdentifier(id, "localhost", port, "localhost", port + 1, "localhost", port + 2, port + 3, true);
+ }
+
+ private ConnectorEntity createConnectorEntity(final String id, final String state) {
+ final ConnectorDTO dto = new ConnectorDTO();
+ dto.setId(id);
+ dto.setName("Test Connector");
+ dto.setType("TestConnector");
+ dto.setState(state);
+
+ final PermissionsDTO permissions = new PermissionsDTO();
+ permissions.setCanRead(true);
+ permissions.setCanWrite(true);
+
+ final ConnectorEntity entity = new ConnectorEntity();
+ entity.setComponent(dto);
+ entity.setRevision(createNewRevision());
+ entity.setPermissions(permissions);
+
+ return entity;
+ }
+
+ private ConnectorEntity createConnectorEntityWithConfig(final String id, final String state,
+ final Map<String, List<String>> propertyAllowableValues) {
+ final ConnectorEntity entity = createConnectorEntity(id, state);
+
+ final ConnectorConfigurationDTO config = new ConnectorConfigurationDTO();
+ final ConfigurationStepConfigurationDTO step = createConfigurationStep("step1", propertyAllowableValues);
+ config.setConfigurationStepConfigurations(List.of(step));
+
+ entity.getComponent().setActiveConfiguration(config);
+ entity.getComponent().setWorkingConfiguration(config);
+
+ return entity;
+ }
+
+ private ConnectorEntity createConnectorEntityWithMultipleSteps() {
+ final ConnectorEntity entity = createConnectorEntity("connector1", "STOPPED");
+
+ final ConnectorConfigurationDTO config = new ConnectorConfigurationDTO();
+ final ConfigurationStepConfigurationDTO step1 = createConfigurationStep("step1",
+ Map.of("hostname", List.of("localhost")));
+ final ConfigurationStepConfigurationDTO step2 = createConfigurationStep("step2",
+ Map.of("port", List.of("8080", "9090")));
+ config.setConfigurationStepConfigurations(List.of(step1, step2));
+
+ entity.getComponent().setActiveConfiguration(config);
+ entity.getComponent().setWorkingConfiguration(config);
+
+ return entity;
+ }
+
+ private ConfigurationStepConfigurationDTO createConfigurationStep(final String stepName,
+ final Map<String, List<String>> propertyAllowableValues) {
+ final ConfigurationStepConfigurationDTO step = new ConfigurationStepConfigurationDTO();
+ step.setConfigurationStepName(stepName);
+ step.setConfigurationStepDescription("Test step");
+
+ final PropertyGroupConfigurationDTO propertyGroup = new PropertyGroupConfigurationDTO();
+ propertyGroup.setPropertyGroupName("default");
+
+ final Map<String, ConnectorPropertyDescriptorDTO> descriptors = new HashMap<>();
+ for (final Map.Entry<String, List<String>> entry : propertyAllowableValues.entrySet()) {
+ final ConnectorPropertyDescriptorDTO descriptor = new ConnectorPropertyDescriptorDTO();
+ descriptor.setName(entry.getKey());
+ descriptor.setDescription("Test property");
+ descriptor.setType("STRING");
+
+ final List<AllowableValueEntity> allowableValues = entry.getValue().stream()
+ .map(value -> {
+ final AllowableValueDTO allowableValueDto = new AllowableValueDTO();
+ allowableValueDto.setValue(value);
+
+ final AllowableValueEntity allowableEntity = new AllowableValueEntity();
+ allowableEntity.setAllowableValue(allowableValueDto);
+ allowableEntity.setCanRead(true);
+ return allowableEntity;
+ })
+ .toList();
+
+ descriptor.setAllowableValues(allowableValues);
+ descriptors.put(entry.getKey(), descriptor);
+ }
+
+ propertyGroup.setPropertyDescriptors(descriptors);
+ propertyGroup.setPropertyValues(new HashMap<>());
+
+ step.setPropertyGroupConfigurations(List.of(propertyGroup));
+
+ return step;
+ }
+
+ private RevisionDTO createNewRevision() {
+ final RevisionDTO revisionDto = new RevisionDTO();
+ revisionDto.setClientId(getClass().getName());
+ revisionDto.setVersion(0L);
+ return revisionDto;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorPropertyDescriptorDtoMergerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorPropertyDescriptorDtoMergerTest.java
new file mode 100644
index 0000000000..b2c4ad2527
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ConnectorPropertyDescriptorDtoMergerTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.util.EqualsWrapper;
+import org.apache.nifi.web.api.dto.AllowableValueDTO;
+import org.apache.nifi.web.api.dto.ConnectorPropertyDescriptorDTO;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class ConnectorPropertyDescriptorDtoMergerTest {
+
+ @Test
+ void testMergeWithNoAllowableValues() {
+ final ConnectorPropertyDescriptorDTO clientPropertyDescriptor = new ConnectorPropertyDescriptorDTO();
+
+ final Map<NodeIdentifier, ConnectorPropertyDescriptorDTO> dtoMap =
+ Map.of(createNodeIdentifier("node1"), new ConnectorPropertyDescriptorDTO(),
+ createNodeIdentifier("node2"), new ConnectorPropertyDescriptorDTO());
+
+ ConnectorPropertyDescriptorDtoMerger.merge(clientPropertyDescriptor, dtoMap);
+
+ assertNull(clientPropertyDescriptor.getAllowableValues());
+ }
+
+ @Test
+ void testMergeWithEmptyAllowableValuesList() {
+ testMerge(
+ createPropertyDescriptorDTO(),
+ Map.of(createNodeIdentifier("node1"), createPropertyDescriptorDTO(),
+ createNodeIdentifier("node2"), createPropertyDescriptorDTO()),
+ createPropertyDescriptorDTO()
+ );
+ }
+
+ @Test
+ void testMergeWithSingleNode() {
+ testMerge(
+ createPropertyDescriptorDTO(v("value1"), v("value2")),
+ Collections.emptyMap(),
+ createPropertyDescriptorDTO(v("value1"), v("value2"))
+ );
+ }
+
+ @Test
+ void testMergeWithNonOverlappingAllowableValues() {
+ testMerge(
+ createPropertyDescriptorDTO(v("value1"), v("value2")),
+ Map.of(createNodeIdentifier("node1"), createPropertyDescriptorDTO(v("value3")),
+ createNodeIdentifier("node2"), createPropertyDescriptorDTO(v("value4"), v("value5"), v("value6"))),
+ createPropertyDescriptorDTO()
+ );
+ }
+
+ @Test
+ void testMergeWithOverlappingAllowableValues() {
+ testMerge(
+ createPropertyDescriptorDTO(v("value1"), v("value2"), v("value3")),
+ Map.of(createNodeIdentifier("node1"), createPropertyDescriptorDTO(v("value1"), v("value2"), v("value3")),
+ createNodeIdentifier("node2"), createPropertyDescriptorDTO(v("value2"), v("value3", false))),
+ createPropertyDescriptorDTO(v("value2"), v("value3", false))
+ );
+ }
+
+ @Test
+ void testMergeWithIdenticalAllowableValues() {
+ testMerge(
+ createPropertyDescriptorDTO(v("value1"), v("value2")),
+ Map.of(createNodeIdentifier("node1"), createPropertyDescriptorDTO(v("value1"), v("value2")),
+ createNodeIdentifier("node2"), createPropertyDescriptorDTO(v("value1"), v("value2"))),
+ createPropertyDescriptorDTO(v("value1"), v("value2"))
+ );
+ }
+
+ @Test
+ void testMergeWithDuplicateAllowableValues() {
+ testMerge(
+ createPropertyDescriptorDTO(v("value1"), v("value1")),
+ Map.of(createNodeIdentifier("node1"), createPropertyDescriptorDTO(v("value1"), v("value1")),
+ createNodeIdentifier("node2"), createPropertyDescriptorDTO(v("value1"), v("value1"))),
+ createPropertyDescriptorDTO(v("value1"), v("value1"))
+ );
+ }
+
+ private ConnectorPropertyDescriptorDTO createPropertyDescriptorDTO(final AllowableValueData... allowableValueData) {
+ final ConnectorPropertyDescriptorDTO clientPropertyDescriptor = new ConnectorPropertyDescriptorDTO();
+
+ final List<AllowableValueEntity> allowableValueEntities = Arrays.stream(allowableValueData)
+ .map(AllowableValueData::toEntity)
+ .collect(Collectors.toList());
+
+ clientPropertyDescriptor.setAllowableValues(allowableValueEntities);
+
+ return clientPropertyDescriptor;
+ }
+
+ private NodeIdentifier createNodeIdentifier(final String id) {
+ return new NodeIdentifier(id, id, 1, id, 1, id, 1, null, false);
+ }
+
+ private void testMerge(final ConnectorPropertyDescriptorDTO clientPropertyDescriptor,
+ final Map<NodeIdentifier, ConnectorPropertyDescriptorDTO> dtoMap,
+ final ConnectorPropertyDescriptorDTO expected) {
+ ConnectorPropertyDescriptorDtoMerger.merge(clientPropertyDescriptor, dtoMap);
+
+ final List<Function<AllowableValueEntity, Object>> equalsProperties = Arrays.asList(
+ AllowableValueEntity::getAllowableValue,
+ AllowableValueEntity::getCanRead
+ );
+
+ final List<EqualsWrapper<AllowableValueEntity>> expectedWrappers = EqualsWrapper.wrapList(expected.getAllowableValues(), equalsProperties);
+ final List<EqualsWrapper<AllowableValueEntity>> actualWrappers = EqualsWrapper.wrapList(clientPropertyDescriptor.getAllowableValues(), equalsProperties);
+
+ assertEquals(expectedWrappers, actualWrappers);
+ }
+
+ private static class AllowableValueData {
+ private final String value;
+ private final Boolean canRead;
+
+ private AllowableValueData(final String value, final Boolean canRead) {
+ this.value = value;
+ this.canRead = canRead;
+ }
+
+ private AllowableValueEntity toEntity() {
+ final AllowableValueEntity entity = new AllowableValueEntity();
+
+ final AllowableValueDTO allowableValueDTO = new AllowableValueDTO();
+ allowableValueDTO.setValue(value);
+
+ entity.setAllowableValue(allowableValueDTO);
+ entity.setCanRead(canRead);
+
+ return entity;
+ }
+ }
+
+ private static AllowableValueData v(final String value) {
+ return v(value, true);
+ }
+
+ private static AllowableValueData v(final String value, final Boolean canRead) {
+ return new AllowableValueData(value, canRead);
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index 8e5c846dc4..e43e2423e4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -19,6 +19,9 @@ package org.apache.nifi.controller;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.annotation.behavior.SupportsBatching;
import org.apache.nifi.annotation.configuration.DefaultSettings;
import org.apache.nifi.bundle.Bundle;
@@ -497,17 +500,29 @@ public class ExtensionBuilder {
final FrameworkConnectorInitializationContext initContext = createConnectorInitializationContext(managedProcessGroup, componentLog);
- final ConnectorNode connectorNode = new StandardConnectorNode(
- identifier,
- flowController.getFlowManager(),
- extensionManager,
- flowController,
- connectorDetails,
- componentType,
- activeConfigurationContext,
- connectorStateTransition,
- flowContextFactory
- );
+ final Authorizable connectorsAuthorizable = new Authorizable() {
+ @Override
+ public Authorizable getParentAuthorizable() {
+ return null;
+ }
+
+ @Override
+ public Resource getResource() {
+ return ResourceFactory.getConnectorsResource();
+ }
+ };
+
+ final ConnectorNode connectorNode = new StandardConnectorNode(
+ identifier,
+ flowController.getFlowManager(),
+ extensionManager,
+ connectorsAuthorizable,
+ connectorDetails,
+ componentType,
+ activeConfigurationContext,
+ connectorStateTransition,
+ flowContextFactory
+ );
initializeDefaultValues(connector, connectorNode.getActiveFlowContext());
// TODO: If an Exception is thrown in the call to #initialize, we should create a Ghosted Connector
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
index 0c0b4c4fc5..959a0f32ee 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -61,6 +61,7 @@ import org.apache.nifi.ui.extension.contentviewer.SupportedMimeTypes;
import org.apache.nifi.util.FileUtils;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.ContentAccess;
+import org.apache.nifi.web.NiFiConnectorWebContext;
import org.apache.nifi.web.NiFiWebConfigurationContext;
import org.apache.nifi.web.UiExtensionType;
import org.apache.nifi.web.server.filter.FilterParameter;
@@ -96,6 +97,7 @@ import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;
@@ -114,6 +116,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
@@ -194,6 +197,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
// component (processor, controller service, reporting task) ui extensions
private UiExtensionMapping componentUiExtensions;
private Collection<WebAppContext> componentUiExtensionWebContexts;
+ private Collection<WebAppContext> connectorUiExtensionWebContexts;
private final Map<BundleCoordinate, List<WebAppContext>> webAppsByBundleCoordinate = new ConcurrentHashMap<>();
@@ -269,6 +273,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
final ExtensionUiInfo extensionUiInfo = loadWars(otherWars);
componentUiExtensionWebContexts = new ArrayList<>(extensionUiInfo.componentUiExtensionWebContexts());
contentViewerWebContexts = new ArrayList<>(extensionUiInfo.contentViewerWebContexts());
+ connectorUiExtensionWebContexts = new ArrayList<>(extensionUiInfo.connectorUiExtensionWebContexts());
contentViewers = new HashSet<>(extensionUiInfo.contentViewers());
componentUiExtensions = new UiExtensionMapping(extensionUiInfo.componentUiExtensionsByType());
@@ -328,15 +333,18 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
final Collection<WebAppContext> componentUiExtensionWebContexts = extensionUiInfo.componentUiExtensionWebContexts();
final Collection<WebAppContext> contentViewerWebContexts = extensionUiInfo.contentViewerWebContexts();
+ final Collection<WebAppContext> connectorUiExtensionWebContexts = extensionUiInfo.connectorUiExtensionWebContexts();
// Inject the configuration context and security filter into contexts that need it
final ServletContext webApiServletContext = webApiContext.getServletHandler().getServletContext();
final WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(webApiServletContext);
final NiFiWebConfigurationContext configurationContext = webApplicationContext.getBean("nifiWebConfigurationContext", NiFiWebConfigurationContext.class);
+ final NiFiConnectorWebContext connectorWebContext = webApplicationContext.getBean("nifiConnectorWebContext", NiFiConnectorWebContext.class);
final FilterHolder securityFilter = webApiContext.getServletHandler().getFilter(SPRING_SECURITY_FILTER_CHAIN);
performInjectionForComponentUis(componentUiExtensionWebContexts, configurationContext, securityFilter);
performInjectionForContentViewerUis(contentViewerWebContexts, webApplicationContext, securityFilter);
+ performInjectionForConnectorUis(connectorUiExtensionWebContexts, connectorWebContext, securityFilter);
// Merge results of current loading into previously loaded results...
this.componentUiExtensionWebContexts.addAll(componentUiExtensionWebContexts);
@@ -417,6 +425,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
final Map<String, String> mimeMappings = new HashMap<>();
final Collection<WebAppContext> componentUiExtensionWebContexts = new ArrayList<>();
final Collection<WebAppContext> contentViewerWebContexts = new ArrayList<>();
+ final Collection<WebAppContext> connectorUiExtensionWebContexts = new ArrayList<>();
final Map<String, List<UiExtension>> componentUiExtensionsByType = new HashMap<>();
final Map<String, ServletContext> contentViewerServletContexts = new HashMap<>();
final Map<BundleCoordinate, List<WebAppContext>> webAppContextsByBundleCoordinate = new HashMap<>();
@@ -433,8 +442,11 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
final Map<UiExtensionType, List<String>> uiExtensionInWar = new HashMap<>();
identifyUiExtensionsForComponents(uiExtensionInWar, war);
+ // identify connector UI extensions from manifest (has route information)
+ final Map<String, Map<String, String>> connectorExtensions = readConnectorUiExtensions(war);
+
// only include wars that are for custom processor ui's
- if (!uiExtensionInWar.isEmpty()) {
+ if (!uiExtensionInWar.isEmpty() || !connectorExtensions.isEmpty()) {
// get the context path
String warName = StringUtils.substringBeforeLast(war.getName(), ".");
String warContextPath = String.format("/%s", warName);
@@ -501,6 +513,35 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
}
}
+ // process connector UI extensions with route information
+ for (final Map.Entry<String, Map<String, String>> connectorEntry : connectorExtensions.entrySet()) {
+ final String connectorType = connectorEntry.getKey();
+ final Map<String, String> routes = connectorEntry.getValue();
+
+ // build a bundle-specific key to support multiple versions of the same connector type
+ final BundleCoordinate coordinate = warBundle.getBundleDetails().getCoordinate();
+ final String bundleSpecificKey = connectorType + ":" + coordinate.getGroup() + ":" + coordinate.getId() + ":" + coordinate.getVersion();
+
+ logger.info("Loading Connector UI extension [{}, {}] for {} with routes {}", UiExtensionType.Connector, warContextPath, bundleSpecificKey, routes.keySet());
+
+ // record the extension definition with route information
+ final UiExtension uiExtension = new UiExtension(UiExtensionType.Connector, warContextPath, routes);
+
+ // create if this is the first extension for this component type (using bundle-specific key)
+ final List<UiExtension> componentUiExtensionsForType = componentUiExtensionsByType.computeIfAbsent(bundleSpecificKey, k -> new ArrayList<>());
+
+ // see if there is already a ui extension of this same type
+ if (containsUiExtensionType(componentUiExtensionsForType, UiExtensionType.Connector)) {
+ throw new IllegalStateException(String.format("Encountered duplicate Connector UI for %s", bundleSpecificKey));
+ }
+
+ // record this extension
+ componentUiExtensionsForType.add(uiExtension);
+
+ // this ui extension provides a connector custom ui
+ connectorUiExtensionWebContexts.add(extensionUiContext);
+ }
+
// include custom ui web context in the handlers
webAppContexts.add(extensionUiContext);
@@ -512,7 +553,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
}
return new ExtensionUiInfo(webAppContexts, mimeMappings, contentViewers, componentUiExtensionWebContexts, contentViewerWebContexts,
- componentUiExtensionsByType, contentViewerServletContexts, webAppContextsByBundleCoordinate);
+ connectorUiExtensionWebContexts, componentUiExtensionsByType, contentViewerServletContexts, webAppContextsByBundleCoordinate);
}
/**
@@ -593,6 +634,58 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
}
}
+ /**
+ * Reads Connector UI extension definitions from a nifi-connector properties file in the WAR.
+ * The properties file format is:
+ * <pre>
+ * # connector-type.route-name=route-path
+ * org.apache.nifi.connectors.KafkaToS3.configuration=#/configure
+ * org.apache.nifi.connectors.KafkaToS3.details=#/details
+ * </pre>
+ *
+ * @param warFile the WAR file to inspect
+ * @return a map of connector type to route map (route name to route path), or empty map if no manifest found
+ */
+ private Map<String, Map<String, String>> readConnectorUiExtensions(final File warFile) {
+ final Map<String, Map<String, String>> connectorExtensions = new HashMap<>();
+
+ try (final JarFile jarFile = new JarFile(warFile)) {
+ final JarEntry propertiesEntry = jarFile.getJarEntry("META-INF/nifi-connector");
+ if (propertiesEntry == null) {
+ return connectorExtensions;
+ }
+
+ try (final InputStream inputStream = jarFile.getInputStream(propertiesEntry)) {
+ final Properties properties = new Properties();
+ properties.load(inputStream);
+
+ for (final String propertyName : properties.stringPropertyNames()) {
+ final String propertyValue = properties.getProperty(propertyName);
+ if (propertyValue == null || propertyValue.isBlank()) {
+ continue;
+ }
+
+ // Parse property name: connector-type.route-name
+ final int lastDotIndex = propertyName.lastIndexOf('.');
+ if (lastDotIndex <= 0 || lastDotIndex >= propertyName.length() - 1) {
+ logger.warn("Invalid connector UI property format: {}. Expected format: connector-type.route-name=route-path", propertyName);
+ continue;
+ }
+
+ final String connectorType = propertyName.substring(0, lastDotIndex);
+ final String routeName = propertyName.substring(lastDotIndex + 1);
+
+ final Map<String, String> routes = connectorExtensions.computeIfAbsent(connectorType, k -> new HashMap<>());
+ routes.put(routeName, propertyValue);
+ }
+ }
+ } catch (final IOException ioe) {
+ logger.warn("Unable to inspect {} for Connector UI extensions.", warFile, ioe);
+ }
+
+ return connectorExtensions;
+ }
+
/**
* Extracts the component type. Trims the line and considers comments.
* Returns null if no type was found.
@@ -803,11 +896,15 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
// get the application context
final WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(webApiServletContext);
final NiFiWebConfigurationContext configurationContext = webApplicationContext.getBean("nifiWebConfigurationContext", NiFiWebConfigurationContext.class);
+ final NiFiConnectorWebContext connectorWebContext = webApplicationContext.getBean("nifiConnectorWebContext", NiFiConnectorWebContext.class);
final FilterHolder securityFilter = webApiContext.getServletHandler().getFilter(SPRING_SECURITY_FILTER_CHAIN);
// component ui extensions
performInjectionForComponentUis(componentUiExtensionWebContexts, configurationContext, securityFilter);
+ // connector ui extensions
+ performInjectionForConnectorUis(connectorUiExtensionWebContexts, connectorWebContext, securityFilter);
+
// content viewer extensions
performInjectionForContentViewerUis(contentViewerWebContexts, webApplicationContext, securityFilter);
webApiServletContext.setAttribute("content-viewers", contentViewers);
@@ -949,6 +1046,21 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
}
}
+ private void performInjectionForConnectorUis(final Collection<WebAppContext> connectorUiExtensionWebContexts,
+ final NiFiConnectorWebContext connectorWebContext,
+ final FilterHolder securityFilter) {
+ for (final WebAppContext customUiContext : connectorUiExtensionWebContexts) {
+ // set the connector web context in each connector custom ui servlet context
+ final ServletContext customUiServletContext = customUiContext.getServletHandler().getServletContext();
+ customUiServletContext.setAttribute("nifi-connector-web-context", connectorWebContext);
+
+ // add the security filter to any ui extensions wars
+ if (securityFilter != null) {
+ customUiContext.addFilter(securityFilter, "/*", EnumSet.allOf(DispatcherType.class));
+ }
+ }
+ }
+
private void performInjectionForContentViewerUis(final Collection<WebAppContext> contentViewerWebContexts,
final WebApplicationContext webApiApplicationContext,
final FilterHolder securityFilter) {
@@ -1018,6 +1130,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
private record ExtensionUiInfo(Collection<WebAppContext> webAppContexts, Map<String, String> mimeMappings,
Collection<ContentViewer> contentViewers, Collection<WebAppContext> componentUiExtensionWebContexts,
Collection<WebAppContext> contentViewerWebContexts,
+ Collection<WebAppContext> connectorUiExtensionWebContexts,
Map<String, List<UiExtension>> componentUiExtensionsByType,
Map<String, ServletContext> contentViewerServletContexts,
Map<BundleCoordinate, List<WebAppContext>> webAppContextsByBundleCoordinate) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java
index da921b4535..11268a99c4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-ui-extension/src/main/java/org/apache/nifi/ui/extension/UiExtension.java
@@ -18,6 +18,8 @@ package org.apache.nifi.ui.extension;
import org.apache.nifi.web.UiExtensionType;
+import java.util.Map;
+
/**
* Information about a UI extension required to be invoked.
*/
@@ -25,10 +27,16 @@ public class UiExtension {
private final UiExtensionType extensionType;
private final String contextPath;
+ private final Map<String, String> supportedRoutes;
public UiExtension(final UiExtensionType extensionType, final String contextPath) {
+ this(extensionType, contextPath, null);
+ }
+
+ public UiExtension(final UiExtensionType extensionType, final String contextPath, final Map<String, String> supportedRoutes) {
this.extensionType = extensionType;
this.contextPath = contextPath;
+ this.supportedRoutes = supportedRoutes;
}
/**
@@ -45,4 +53,12 @@ public class UiExtension {
return contextPath;
}
+ /**
+ * @return A map of route names to route paths for this UI extension, or null if routes are not applicable.
+ * Route paths may include a hash prefix for hash-based routing (e.g., "#/wizard") or be path-based (e.g., "/wizard").
+ */
+ public Map<String, String> getSupportedRoutes() {
+ return supportedRoutes;
+ }
+
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
new file mode 100644
index 0000000000..18785143dc
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
@@ -0,0 +1,492 @@
+/*
+ * 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.audit;
+
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.Component;
+import org.apache.nifi.action.FlowChangeAction;
+import org.apache.nifi.action.Operation;
+import org.apache.nifi.action.component.details.FlowChangeExtensionDetails;
+import org.apache.nifi.action.details.ActionDetails;
+import org.apache.nifi.action.details.FlowChangeConfigureDetails;
+import org.apache.nifi.components.connector.AssetReference;
+import org.apache.nifi.components.connector.ConfigurationStepConfiguration;
+import org.apache.nifi.components.connector.ConnectorConfiguration;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorState;
+import org.apache.nifi.components.connector.ConnectorValueReference;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.StringLiteralValue;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+import org.apache.nifi.web.dao.ConnectorDAO;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Audits connector creation/removal and configuration changes.
+ */
+@Service
+@Aspect
+public class ConnectorAuditor extends NiFiAuditor {
+
+ private static final Logger logger = LoggerFactory.getLogger(ConnectorAuditor.class);
+
+ /**
+ * Audits the creation of connectors via createConnector().
+ *
+ * @param proceedingJoinPoint join point
+ * @return connector node
+ * @throws Throwable if an error occurs
+ */
+ @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
+ + "execution(org.apache.nifi.components.connector.ConnectorNode createConnector(..))")
+ public ConnectorNode createConnectorAdvice(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+ final ConnectorNode connector = (ConnectorNode) proceedingJoinPoint.proceed();
+
+ final Action action = generateAuditRecord(connector, Operation.Add);
+ if (action != null) {
+ saveAction(action, logger);
+ }
+
+ return connector;
+ }
+
+ /**
+ * Audits the removal of a connector via deleteConnector().
+ *
+ * @param proceedingJoinPoint join point
+ * @param connectorId connector id
+ * @param connectorDAO connector dao
+ * @throws Throwable if an error occurs
+ */
+ @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
+ + "execution(void deleteConnector(java.lang.String)) && "
+ + "args(connectorId) && "
+ + "target(connectorDAO)")
+ public void removeConnectorAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final ConnectorDAO connectorDAO) throws Throwable {
+ final ConnectorNode connector = connectorDAO.getConnector(connectorId);
+
+ proceedingJoinPoint.proceed();
+
+ final Action action = generateAuditRecord(connector, Operation.Remove);
+ if (action != null) {
+ saveAction(action, logger);
+ }
+ }
+
+ /**
+ * Audits the starting of a connector via startConnector().
+ *
+ * @param proceedingJoinPoint join point
+ * @param connectorId connector id
+ * @param connectorDAO connector dao
+ * @throws Throwable if an error occurs
+ */
+ @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
+ + "execution(void startConnector(java.lang.String)) && "
+ + "args(connectorId) && "
+ + "target(connectorDAO)")
+ public void startConnectorAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final ConnectorDAO connectorDAO) throws Throwable {
+ final ConnectorNode connector = connectorDAO.getConnector(connectorId);
+ final ConnectorState previousState = connector.getCurrentState();
+
+ proceedingJoinPoint.proceed();
+
+ if (isAuditable() && previousState != ConnectorState.RUNNING && previousState != ConnectorState.STARTING) {
+ final Action action = generateAuditRecord(connector, Operation.Start);
+ if (action != null) {
+ saveAction(action, logger);
+ }
+ }
+ }
+
+ /**
+ * Audits the stopping of a connector via stopConnector().
+ *
+ * @param proceedingJoinPoint join point
+ * @param connectorId connector id
+ * @param connectorDAO connector dao
+ * @throws Throwable if an error occurs
+ */
+ @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
+ + "execution(void stopConnector(java.lang.String)) && "
+ + "args(connectorId) && "
+ + "target(connectorDAO)")
+ public void stopConnectorAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final ConnectorDAO connectorDAO) throws Throwable {
+ final ConnectorNode connector = connectorDAO.getConnector(connectorId);
+ final ConnectorState previousState = connector.getCurrentState();
+
+ proceedingJoinPoint.proceed();
+
+ if (isAuditable() && previousState != ConnectorState.STOPPED && previousState != ConnectorState.STOPPING) {
+ final Action action = generateAuditRecord(connector, Operation.Stop);
+ if (action != null) {
+ saveAction(action, logger);
+ }
+ }
+ }
+
+ /**
+ * Audits the enabling of a connector via enableConnector().
+ *
+ * @param proceedingJoinPoint join point
+ * @param connectorId connector id
+ * @param connectorDAO connector dao
+ * @throws Throwable if an error occurs
+ */
+ @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
+ + "execution(void enableConnector(java.lang.String)) && "
+ + "args(connectorId) && "
+ + "target(connectorDAO)")
+ public void enableConnectorAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final ConnectorDAO connectorDAO) throws Throwable {
+ final ConnectorNode connector = connectorDAO.getConnector(connectorId);
+ final ConnectorState previousState = connector.getCurrentState();
+
+ proceedingJoinPoint.proceed();
+
+ if (isAuditable() && previousState == ConnectorState.DISABLED) {
+ final Action action = generateAuditRecord(connector, Operation.Enable);
+ if (action != null) {
+ saveAction(action, logger);
+ }
+ }
+ }
+
+ /**
+ * Audits the disabling of a connector via disableConnector().
+ *
+ * @param proceedingJoinPoint join point
+ * @param connectorId connector id
+ * @param connectorDAO connector dao
+ * @throws Throwable if an error occurs
+ */
+ @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
+ + "execution(void disableConnector(java.lang.String)) && "
+ + "args(connectorId) && "
+ + "target(connectorDAO)")
+ public void disableConnectorAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final ConnectorDAO connectorDAO) throws Throwable {
+ final ConnectorNode connector = connectorDAO.getConnector(connectorId);
+ final ConnectorState previousState = connector.getCurrentState();
+
+ proceedingJoinPoint.proceed();
+
+ if (isAuditable() && previousState != ConnectorState.DISABLED) {
+ final Action action = generateAuditRecord(connector, Operation.Disable);
+ if (action != null) {
+ saveAction(action, logger);
+ }
+ }
+ }
+
+ /**
+ * Audits configuration step updates via updateConnectorConfigurationStep().
+ *
+ * @param proceedingJoinPoint join point
+ * @param connectorId connector id
+ * @param configurationStepName name of the configuration step
+ * @param configurationStepConfiguration the configuration step configuration DTO
+ * @param connectorDAO connector dao
+ * @throws Throwable if an error occurs
+ */
+ @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
+ + "execution(void updateConnectorConfigurationStep(java.lang.String, java.lang.String, org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO)) && "
+ + "args(connectorId, configurationStepName, configurationStepConfiguration) && "
+ + "target(connectorDAO)")
+ public void updateConfigurationStepAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final String configurationStepName,
+ final ConfigurationStepConfigurationDTO configurationStepConfiguration, final ConnectorDAO connectorDAO) throws Throwable {
+ final ConnectorNode connector = connectorDAO.getConnector(connectorId);
+
+ // Capture the current property values before the update
+ final Map<String, Map<String, String>> previousValues = extractCurrentPropertyValues(connector, configurationStepName);
+
+ proceedingJoinPoint.proceed();
+
+ if (isAuditable()) {
+ // Extract the new property values from the DTO
+ final Map<String, Map<String, String>> newValues = extractPropertyValuesFromDto(configurationStepConfiguration);
+
+ // Generate audit actions for each changed property
+ final List<Action> actions = generateConfigurationChangeActions(connector, configurationStepName, previousValues, newValues);
+ if (!actions.isEmpty()) {
+ saveActions(actions, logger);
+ }
+ }
+ }
+
+ /**
+ * Extracts the current property values for a specific configuration step from the connector's working flow context.
+ *
+ * @param connector the connector node
+ * @param configurationStepName the name of the configuration step
+ * @return a map of property group name to property name to property value
+ */
+ private Map<String, Map<String, String>> extractCurrentPropertyValues(final ConnectorNode connector, final String configurationStepName) {
+ final Map<String, Map<String, String>> result = new HashMap<>();
+
+ final ConnectorConfiguration configuration = connector.getWorkingFlowContext().getConfigurationContext().toConnectorConfiguration();
+ for (final ConfigurationStepConfiguration stepConfig : configuration.getConfigurationStepConfigurations()) {
+ if (Objects.equals(stepConfig.stepName(), configurationStepName)) {
+ for (final PropertyGroupConfiguration groupConfig : stepConfig.propertyGroupConfigurations()) {
+ final String groupName = groupConfig.groupName();
+ final Map<String, String> propertyValues = new HashMap<>();
+
+ for (final Map.Entry<String, ConnectorValueReference> entry : groupConfig.propertyValues().entrySet()) {
+ propertyValues.put(entry.getKey(), formatValueReference(entry.getValue()));
+ }
+
+ result.put(groupName, propertyValues);
+ }
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Extracts property values from the configuration step DTO.
+ *
+ * @param configurationStepDto the configuration step DTO
+ * @return a map of property group name to property name to property value
+ */
+ private Map<String, Map<String, String>> extractPropertyValuesFromDto(final ConfigurationStepConfigurationDTO configurationStepDto) {
+ final Map<String, Map<String, String>> result = new HashMap<>();
+
+ if (configurationStepDto.getPropertyGroupConfigurations() != null) {
+ for (final PropertyGroupConfigurationDTO groupDto : configurationStepDto.getPropertyGroupConfigurations()) {
+ final String groupName = groupDto.getPropertyGroupName();
+ final Map<String, String> propertyValues = new HashMap<>();
+
+ if (groupDto.getPropertyValues() != null) {
+ for (final Map.Entry<String, ConnectorValueReferenceDTO> entry : groupDto.getPropertyValues().entrySet()) {
+ propertyValues.put(entry.getKey(), formatValueReferenceDto(entry.getValue()));
+ }
+ }
+
+ result.put(groupName, propertyValues);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Formats a ConnectorValueReference as a string for audit logging.
+ *
+ * @param valueRef the value reference
+ * @return the formatted string representation
+ */
+ private String formatValueReference(final ConnectorValueReference valueRef) {
+ if (valueRef == null) {
+ return null;
+ }
+
+ return switch (valueRef) {
+ case StringLiteralValue stringLiteral -> stringLiteral.getValue();
+ case AssetReference assetRef -> "asset:" + assetRef.getAssetIdentifier();
+ case SecretReference secretRef -> "secret:" + secretRef.getProviderId() + "/" + secretRef.getProviderName() + "/" + secretRef.getSecretName();
+ };
+ }
+
+ /**
+ * Formats a ConnectorValueReferenceDTO as a string for audit logging.
+ *
+ * @param valueRefDto the value reference DTO
+ * @return the formatted string representation
+ */
+ private String formatValueReferenceDto(final ConnectorValueReferenceDTO valueRefDto) {
+ if (valueRefDto == null) {
+ return null;
+ }
+
+ final String valueType = valueRefDto.getValueType();
+ if (valueType == null || "STRING_LITERAL".equals(valueType)) {
+ return valueRefDto.getValue();
+ } else if ("ASSET_REFERENCE".equals(valueType)) {
+ return "asset:" + valueRefDto.getAssetIdentifier();
+ } else if ("SECRET_REFERENCE".equals(valueType)) {
+ return "secret:" + valueRefDto.getSecretProviderId() + "/" + valueRefDto.getSecretProviderName() + "/" + valueRefDto.getSecretName();
+ }
+
+ return valueRefDto.getValue();
+ }
+
+ /**
+ * Generates audit actions for configuration changes by comparing previous and new values.
+ *
+ * @param connector the connector node
+ * @param configurationStepName the configuration step name
+ * @param previousValues the previous property values
+ * @param newValues the new property values
+ * @return list of actions for changed properties
+ */
+ private List<Action> generateConfigurationChangeActions(final ConnectorNode connector, final String configurationStepName,
+ final Map<String, Map<String, String>> previousValues, final Map<String, Map<String, String>> newValues) {
+ final List<Action> actions = new ArrayList<>();
+ final Date timestamp = new Date();
+
+ final FlowChangeExtensionDetails connectorDetails = new FlowChangeExtensionDetails();
+ connectorDetails.setType(connector.getComponentType());
+
+ // Iterate through all property groups in the new values
+ for (final Map.Entry<String, Map<String, String>> groupEntry : newValues.entrySet()) {
+ final String groupName = groupEntry.getKey();
+ final Map<String, String> newGroupValues = groupEntry.getValue();
+ final Map<String, String> previousGroupValues = previousValues.getOrDefault(groupName, new HashMap<>());
+
+ // Check each property in this group
+ for (final Map.Entry<String, String> propertyEntry : newGroupValues.entrySet()) {
+ final String propertyName = propertyEntry.getKey();
+ final String newValue = propertyEntry.getValue();
+ final String previousValue = previousGroupValues.get(propertyName);
+
+ // Only create an action if the value changed
+ if (!Objects.equals(previousValue, newValue)) {
+ final String fullPropertyName = formatPropertyName(configurationStepName, groupName, propertyName);
+
+ final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
+ actionDetails.setName(fullPropertyName);
+ actionDetails.setPreviousValue(previousValue);
+ actionDetails.setValue(newValue);
+
+ final FlowChangeAction configurationAction = createFlowChangeAction();
+ configurationAction.setOperation(Operation.Configure);
+ configurationAction.setTimestamp(timestamp);
+ configurationAction.setSourceId(connector.getIdentifier());
+ configurationAction.setSourceName(connector.getName());
+ configurationAction.setSourceType(Component.Connector);
+ configurationAction.setComponentDetails(connectorDetails);
+ configurationAction.setActionDetails(actionDetails);
+
+ actions.add(configurationAction);
+ }
+ }
+ }
+
+ return actions;
+ }
+
+ /**
+ * Formats the property name for audit logging, including step and group context.
+ *
+ * @param stepName the configuration step name
+ * @param groupName the property group name
+ * @param propertyName the property name
+ * @return the formatted property name
+ */
+ private String formatPropertyName(final String stepName, final String groupName, final String propertyName) {
+ if (groupName == null || groupName.isEmpty()) {
+ return stepName + " / " + propertyName;
+ }
+ return stepName + " / " + groupName + " / " + propertyName;
+ }
+
+ /**
+ * Audits application of connector updates via applyConnectorUpdate().
+ *
+ * @param proceedingJoinPoint join point
+ * @param connectorId connector id
+ * @param connectorDAO connector dao
+ * @throws Throwable if an error occurs
+ */
+ @Around("within(org.apache.nifi.web.dao.ConnectorDAO+) && "
+ + "execution(void applyConnectorUpdate(java.lang.String)) && "
+ + "args(connectorId) && "
+ + "target(connectorDAO)")
+ public void applyConnectorUpdateAdvice(final ProceedingJoinPoint proceedingJoinPoint, final String connectorId, final ConnectorDAO connectorDAO) throws Throwable {
+ final ConnectorNode connector = connectorDAO.getConnector(connectorId);
+
+ proceedingJoinPoint.proceed();
+
+ if (isAuditable()) {
+ final FlowChangeExtensionDetails connectorDetails = new FlowChangeExtensionDetails();
+ connectorDetails.setType(connector.getComponentType());
+
+ final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
+ actionDetails.setName("Applied Update");
+ actionDetails.setValue("true");
+ actionDetails.setPreviousValue(null);
+
+ final FlowChangeAction configurationAction = createFlowChangeAction();
+ configurationAction.setOperation(Operation.Configure);
+ configurationAction.setTimestamp(new Date());
+ configurationAction.setSourceId(connector.getIdentifier());
+ configurationAction.setSourceName(connector.getName());
+ configurationAction.setSourceType(Component.Connector);
+ configurationAction.setComponentDetails(connectorDetails);
+ configurationAction.setActionDetails(actionDetails);
+
+ saveAction(configurationAction, logger);
+ }
+ }
+
+ /**
+ * Generates an audit record for a connector.
+ *
+ * @param connector the connector
+ * @param operation the operation
+ * @return the action
+ */
+ public Action generateAuditRecord(final ConnectorNode connector, final Operation operation) {
+ return generateAuditRecord(connector, operation, null);
+ }
+
+ /**
+ * Generates an audit record for a connector.
+ *
+ * @param connector the connector
+ * @param operation the operation
+ * @param actionDetails the action details
+ * @return the action
+ */
+ public Action generateAuditRecord(final ConnectorNode connector, final Operation operation, final ActionDetails actionDetails) {
+ FlowChangeAction action = null;
+
+ if (isAuditable()) {
+ final FlowChangeExtensionDetails connectorDetails = new FlowChangeExtensionDetails();
+ connectorDetails.setType(connector.getComponentType());
+
+ action = createFlowChangeAction();
+ action.setOperation(operation);
+ action.setSourceId(connector.getIdentifier());
+ action.setSourceName(connector.getName());
+ action.setSourceType(Component.Connector);
+ action.setComponentDetails(connectorDetails);
+
+ if (actionDetails != null) {
+ action.setActionDetails(actionDetails);
+ }
+ }
+
+ return action;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java
index 1b004a4ead..8df8c26162 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java
@@ -256,6 +256,12 @@ public interface AuthorizableLookup {
*/
Authorizable getParameterContexts();
+ /**
+ * Get the authorizable for Connectors
+ * @return authorizable
+ */
+ Authorizable getConnectors();
+
/**
* Get the authorizable connectable. Note this does not include RemoteGroupPorts.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java
index ec61f15f77..c9108be105 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java
@@ -58,6 +58,7 @@ import org.apache.nifi.web.api.dto.FlowSnippetDTO;
import org.apache.nifi.web.controller.ControllerFacade;
import org.apache.nifi.web.dao.AccessPolicyDAO;
import org.apache.nifi.web.dao.ConnectionDAO;
+import org.apache.nifi.web.dao.ConnectorDAO;
import org.apache.nifi.web.dao.ControllerServiceDAO;
import org.apache.nifi.web.dao.FlowAnalysisRuleDAO;
import org.apache.nifi.web.dao.FlowRegistryDAO;
@@ -123,6 +124,7 @@ public class StandardAuthorizableLookup implements AuthorizableLookup {
}
};
+
private static final Authorizable RESOURCE_AUTHORIZABLE = new Authorizable() {
@Override
public Authorizable getParentAuthorizable() {
@@ -186,6 +188,7 @@ public class StandardAuthorizableLookup implements AuthorizableLookup {
private PortDAO inputPortDAO;
private PortDAO outputPortDAO;
private ConnectionDAO connectionDAO;
+ private ConnectorDAO connectorDAO;
private ControllerServiceDAO controllerServiceDAO;
private ReportingTaskDAO reportingTaskDAO;
private FlowAnalysisRuleDAO flowAnalysisRuleDAO;
@@ -609,7 +612,7 @@ public class StandardAuthorizableLookup implements AuthorizableLookup {
@Override
public Authorizable getConnector(final String connectorId) {
- return controllerFacade.getFlowManager().getConnector(connectorId);
+ return connectorDAO.getConnector(connectorId);
}
private Authorizable handleResourceTypeContainingOtherResourceType(final String resource, final ResourceType resourceType) {
@@ -655,6 +658,7 @@ public class StandardAuthorizableLookup implements AuthorizableLookup {
case FlowAnalysisRule -> getFlowAnalysisRule(componentId).getAuthorizable();
case ParameterContext -> getParameterContext(componentId);
case ParameterProvider -> getParameterProvider(componentId).getAuthorizable();
+ case Connector -> getConnector(componentId);
default -> null;
};
@@ -741,6 +745,9 @@ public class StandardAuthorizableLookup implements AuthorizableLookup {
case ParameterContext:
authorizable = getParameterContexts();
break;
+ case Connector:
+ authorizable = getConnectors();
+ break;
}
if (authorizable == null) {
@@ -833,6 +840,21 @@ public class StandardAuthorizableLookup implements AuthorizableLookup {
};
}
+ @Override
+ public Authorizable getConnectors() {
+ return new Authorizable() {
+ @Override
+ public Authorizable getParentAuthorizable() {
+ return null;
+ }
+
+ @Override
+ public Resource getResource() {
+ return ResourceFactory.getConnectorsResource();
+ }
+ };
+ }
+
/**
* ComponentAuthorizable for a ConfigurableComponent. This authorizable is intended only to be used when
* creating new components.
@@ -1414,6 +1436,11 @@ public class StandardAuthorizableLookup implements AuthorizableLookup {
this.connectionDAO = connectionDAO;
}
+ @Autowired
+ public void setConnectorDAO(ConnectorDAO connectorDAO) {
+ this.connectorDAO = connectorDAO;
+ }
+
@Autowired
public void setControllerServiceDAO(ControllerServiceDAO controllerServiceDAO) {
this.controllerServiceDAO = controllerServiceDAO;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index b61bb77981..53232f8222 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -82,6 +82,9 @@ import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.web.api.dto.ResourceDTO;
import org.apache.nifi.web.api.dto.SnippetDTO;
import org.apache.nifi.web.api.dto.SystemDiagnosticsDTO;
@@ -148,6 +151,10 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
import org.apache.nifi.web.api.entity.SnippetEntity;
import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
@@ -178,6 +185,47 @@ import java.util.function.Supplier;
* Defines the NiFiServiceFacade interface.
*/
public interface NiFiServiceFacade {
+ // ----------------------------------------
+ // Connector methods
+ // ----------------------------------------
+
+ void verifyCreateConnector(ConnectorDTO connectorDTO);
+
+ ConnectorEntity createConnector(Revision revision, ConnectorDTO connectorDTO);
+
+ Set<ConnectorEntity> getConnectors();
+
+ ConnectorEntity getConnector(String id);
+
+ void verifyUpdateConnector(ConnectorDTO connectorDTO);
+
+ ConnectorEntity updateConnector(Revision revision, ConnectorDTO connectorDTO);
+
+ void verifyDeleteConnector(String id);
+
+ ConnectorEntity deleteConnector(Revision revision, String id);
+
+ ConnectorEntity scheduleConnector(Revision revision, String id, ScheduledState state);
+
+ ConfigurationStepNamesEntity getConnectorConfigurationSteps(String id);
+
+ ConfigurationStepEntity getConnectorConfigurationStep(String id, String configurationStepName);
+
+ ConfigurationStepEntity updateConnectorConfigurationStep(Revision revision, String id, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
+
+ ConnectorEntity applyConnectorUpdate(Revision revision, String connectorId);
+
+ ProcessGroupFlowEntity getConnectorFlow(String id, boolean uiOnly);
+
+ ProcessGroupStatusEntity getConnectorProcessGroupStatus(String id, Boolean recursive);
+
+ void verifyCanVerifyConnectorConfigurationStep(String connectorId, String configurationStepName);
+
+ List<ConfigVerificationResultDTO> performConnectorConfigurationStepVerification(String connectorId, String configurationStepName, List<PropertyGroupConfigurationDTO> propertyGroupConfigurations);
+
+ SearchResultsDTO searchConnector(String connectorId, String query);
+
+ ConnectorPropertyAllowableValuesEntity getConnectorPropertyAllowableValues(String connectorId, String stepName, String groupName, String propertyName, String filter);
// ----------------------------------------
// Synchronization methods
@@ -477,6 +525,16 @@ public interface NiFiServiceFacade {
*/
Set<DocumentedTypeDTO> getProcessorTypes(final String bundleGroupFilter, final String bundleArtifactFilter, final String typeFilter);
+ /**
+ * Returns the list of connector types.
+ *
+ * @param bundleGroupFilter if specified, must be member of bundle group
+ * @param bundleArtifactFilter if specified, must be member of bundle artifact
+ * @param typeFilter if specified, type must match
+ * @return The list of available connector types matching specified criteria
+ */
+ Set<DocumentedTypeDTO> getConnectorTypes(final String bundleGroupFilter, final String bundleArtifactFilter, final String typeFilter);
+
/**
* Returns the list of controller service types.
*
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java
index 3baf1b9875..978977b13f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiResourceConfig.java
@@ -104,6 +104,7 @@ public class NiFiWebApiResourceConfig extends ResourceConfig {
register(ctx.getBean("versionsResource"));
register(ctx.getBean("parameterContextResource"));
register(ctx.getBean("parameterProviderResource"));
+ register(ctx.getBean("connectorResource"));
// exception mappers
register(AccessDeniedExceptionMapper.class);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java
new file mode 100644
index 0000000000..0a51db60a8
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java
@@ -0,0 +1,44 @@
+/*
+ * 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.web;
+
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.web.dao.ConnectorDAO;
+
+/**
+ * Implements the NiFiConnectorWebContext interface to provide
+ * Connector instances to connector custom UIs.
+ */
+public class StandardNiFiConnectorWebContext implements NiFiConnectorWebContext {
+
+ private ConnectorDAO connectorDAO;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getConnector(final String connectorId) throws IllegalArgumentException {
+ final ConnectorNode connectorNode = connectorDAO.getConnector(connectorId);
+ if (connectorNode == null) {
+ throw new IllegalArgumentException("Unable to find connector with id: " + connectorId);
+ }
+ return (T) connectorNode.getConnector();
+ }
+
+ public void setConnectorDAO(final ConnectorDAO connectorDAO) {
+ this.connectorDAO = connectorDAO;
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index de303d5c33..1fa7e93076 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -71,11 +71,14 @@ import org.apache.nifi.cluster.event.NodeEvent;
import org.apache.nifi.cluster.manager.exception.IllegalNodeDeletionException;
import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.RequiredPermission;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
+import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.components.validation.ValidationState;
@@ -253,6 +256,8 @@ import org.apache.nifi.web.api.dto.DocumentedTypeDTO;
import org.apache.nifi.web.api.dto.DropRequestDTO;
import org.apache.nifi.web.api.dto.DtoFactory;
import org.apache.nifi.web.api.dto.EntityFactory;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.FlowAnalysisRuleDTO;
import org.apache.nifi.web.api.dto.FlowAnalysisRuleViolationDTO;
import org.apache.nifi.web.api.dto.FlowConfigurationDTO;
@@ -282,6 +287,7 @@ import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.ProcessorRunStatusDetailsDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.web.api.dto.PropertyHistoryDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
@@ -323,6 +329,8 @@ import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
import org.apache.nifi.web.api.dto.status.StatusSnapshotDTO;
import org.apache.nifi.web.api.entity.AccessPolicyEntity;
import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.AffectedComponentEntity;
@@ -380,6 +388,9 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
import org.apache.nifi.web.api.entity.SnippetEntity;
import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
@@ -395,6 +406,7 @@ import org.apache.nifi.web.api.entity.VersionedFlowSnapshotMetadataEntity;
import org.apache.nifi.web.api.entity.VersionedReportingTaskImportResponseEntity;
import org.apache.nifi.web.api.request.FlowMetricsRegistry;
import org.apache.nifi.web.api.request.FlowMetricsReportingStrategy;
+import org.apache.nifi.web.dao.ConnectorDAO;
import org.apache.nifi.web.controller.ControllerFacade;
import org.apache.nifi.web.dao.AccessPolicyDAO;
import org.apache.nifi.web.dao.ConnectionDAO;
@@ -489,6 +501,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
private ConnectionDAO connectionDAO;
private ControllerServiceDAO controllerServiceDAO;
private ReportingTaskDAO reportingTaskDAO;
+ private ConnectorDAO connectorDAO;
private FlowAnalysisRuleDAO flowAnalysisRuleDAO;
private ParameterProviderDAO parameterProviderDAO;
private UserDAO userDAO;
@@ -779,6 +792,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
reportingTaskDAO.verifyCreate(reportingTaskDTO);
}
+ @Override
+ public void verifyCreateConnector(final ConnectorDTO connectorDTO) {
+ // For now, no additional verification on DTO here; creation will fail in DAO if invalid
+ }
+
@Override
public void verifyUpdateReportingTask(final ReportingTaskDTO reportingTaskDTO) {
// if tasks does not exist, then the update request is likely creating it
@@ -3458,6 +3476,243 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return entityFactory.createReportingTaskEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
}
+ @Override
+ public ConnectorEntity createConnector(final Revision revision, final ConnectorDTO connectorDTO) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ final BundleCoordinate coordinate = BundleUtils.getCompatibleBundle(controllerFacade.getExtensionManager(), connectorDTO.getType(), connectorDTO.getBundle());
+ final ConnectorNode connector = connectorDAO.createConnector(connectorDTO.getType(), connectorDTO.getId(), coordinate, true, true);
+
+ controllerFacade.save();
+
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(connector);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ConnectorNode connector = connectorDAO.getConnector(snapshot.getComponent().getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connector);
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public Set<ConnectorEntity> getConnectors() {
+ return connectorDAO.getConnectors().stream().map(node -> {
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(node.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
+ return entityFactory.createConnectorEntity(dto, revision, permissions);
+ }).collect(Collectors.toSet());
+ }
+
+ @Override
+ public ConnectorEntity getConnector(final String id) {
+ final ConnectorNode node = connectorDAO.getConnector(id);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(node.getIdentifier()));
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
+ return entityFactory.createConnectorEntity(dto, revision, permissions);
+ }
+
+ @Override
+ public void verifyUpdateConnector(final ConnectorDTO connectorDTO) {
+ // No-op placeholder for future detailed verification
+ }
+
+ @Override
+ public ConnectorEntity updateConnector(final Revision revision, final ConnectorDTO connectorDTO) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ final ConnectorNode node = connectorDAO.getConnector(connectorDTO.getId());
+ if (connectorDTO.getName() != null) node.setName(connectorDTO.getName());
+
+ controllerFacade.save();
+
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public void verifyDeleteConnector(final String id) {
+ // For now, DAO will enforce state; expose hook for symmetry
+ }
+
+ @Override
+ public ConnectorEntity deleteConnector(final Revision revision, final String id) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ final ConnectorNode node = connectorDAO.getConnector(id);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ connectorDAO.deleteConnector(id);
+ controllerFacade.save();
+
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ConnectorDTO dto = snapshot.getComponent();
+ final PermissionsDTO permissions = new PermissionsDTO();
+ permissions.setCanRead(Boolean.FALSE);
+ return entityFactory.createConnectorEntity(dto, dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public ConnectorEntity scheduleConnector(final Revision revision, final String id, final ScheduledState state) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ switch (state) {
+ case RUNNING -> connectorDAO.startConnector(id);
+ case STOPPED -> connectorDAO.stopConnector(id);
+ case DISABLED -> connectorDAO.disableConnector(id);
+ default -> throw new IllegalArgumentException("Unsupported scheduled state for Connector: " + state);
+ }
+ controllerFacade.save();
+
+ final ConnectorNode node = connectorDAO.getConnector(id);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public ConfigurationStepNamesEntity getConnectorConfigurationSteps(final String id) {
+ final ConnectorNode node = connectorDAO.getConnector(id);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+
+ return entityFactory.createConfigurationStepNamesEntity(dto);
+ }
+
+ @Override
+ public ConfigurationStepEntity getConnectorConfigurationStep(final String id, final String configurationStepName) {
+ final ConnectorNode node = connectorDAO.getConnector(id);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(node.getIdentifier()));
+
+ return entityFactory.createConfigurationStepEntity(dto, configurationStepName, revision);
+ }
+
+ @Override
+ public ConfigurationStepEntity updateConnectorConfigurationStep(final Revision revision, final String id,
+ final String configurationStepName, final ConfigurationStepConfigurationDTO configurationStepConfiguration) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ // Update the configuration step
+ connectorDAO.updateConnectorConfigurationStep(id, configurationStepName, configurationStepConfiguration);
+ controllerFacade.save();
+
+ // Return updated connector DTO
+ final ConnectorNode node = connectorDAO.getConnector(id);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ // Create and return the configuration step entity
+ return entityFactory.createConfigurationStepEntity(snapshot.getComponent(), configurationStepName, dtoFactory.createRevisionDTO(snapshot.getLastModification()));
+ }
+
+ @Override
+ public ConnectorEntity applyConnectorUpdate(final Revision revision, final String connectorId) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final RevisionClaim claim = new StandardRevisionClaim(revision);
+
+ final RevisionUpdate<ConnectorDTO> snapshot = revisionManager.updateRevision(claim, user, () -> {
+ connectorDAO.applyConnectorUpdate(connectorId);
+
+ final ConnectorNode node = connectorDAO.getConnector(connectorId);
+ final ConnectorDTO dto = dtoFactory.createConnectorDto(node);
+ final FlowModification lastMod = new FlowModification(revision.incrementRevision(revision.getClientId()), user.getIdentity());
+ return new StandardRevisionUpdate<>(dto, lastMod);
+ });
+
+ final ConnectorNode node = connectorDAO.getConnector(snapshot.getComponent().getId());
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(node);
+ return entityFactory.createConnectorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions);
+ }
+
+ @Override
+ public ProcessGroupFlowEntity getConnectorFlow(final String id, final boolean uiOnly) {
+ final ConnectorNode connectorNode = connectorDAO.getConnector(id);
+ final ProcessGroup managedProcessGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
+ return createProcessGroupFlowEntity(managedProcessGroup, uiOnly);
+ }
+
+ @Override
+ public ProcessGroupStatusEntity getConnectorProcessGroupStatus(final String id, final Boolean recursive) {
+ final ConnectorNode connectorNode = connectorDAO.getConnector(id);
+ final ProcessGroup managedProcessGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
+ final String processGroupId = managedProcessGroup.getIdentifier();
+
+ final PermissionsDTO permissions = dtoFactory.createPermissionsDto(connectorNode);
+ final ProcessGroupStatusDTO dto = dtoFactory.createProcessGroupStatusDto(managedProcessGroup, controllerFacade.getProcessGroupStatus(processGroupId));
+
+ // prune the response as necessary
+ if (!Boolean.TRUE.equals(recursive)) {
+ pruneChildGroups(dto.getAggregateSnapshot());
+ if (dto.getNodeSnapshots() != null) {
+ for (final NodeProcessGroupStatusSnapshotDTO nodeSnapshot : dto.getNodeSnapshots()) {
+ pruneChildGroups(nodeSnapshot.getStatusSnapshot());
+ }
+ }
+ }
+
+ return entityFactory.createProcessGroupStatusEntity(dto, permissions);
+ }
+
+ @Override
+ public void verifyCanVerifyConnectorConfigurationStep(final String connectorId, final String configurationStepName) {
+ connectorDAO.verifyCanVerifyConfigurationStep(connectorId, configurationStepName);
+ }
+
+ @Override
+ public List<ConfigVerificationResultDTO> performConnectorConfigurationStepVerification(final String connectorId,
+ final String configurationStepName, final List<PropertyGroupConfigurationDTO> propertyGroupConfigurations) {
+ final List<ConfigVerificationResult> verificationResults = connectorDAO.verifyConfigurationStep(connectorId, configurationStepName, propertyGroupConfigurations);
+ return verificationResults.stream()
+ .map(result -> dtoFactory.createConfigVerificationResultDto(result))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public SearchResultsDTO searchConnector(final String connectorId, final String query) {
+ final ConnectorNode connectorNode = connectorDAO.getConnector(connectorId);
+ final ProcessGroup managedProcessGroup = connectorNode.getActiveFlowContext().getManagedProcessGroup();
+ return controllerFacade.searchConnector(query, managedProcessGroup);
+ }
+
+ @Override
+ public ConnectorPropertyAllowableValuesEntity getConnectorPropertyAllowableValues(final String connectorId,
+ final String stepName, final String groupName, final String propertyName, final String filter) {
+ final List<AllowableValue> allowableValues = connectorDAO.fetchAllowableValues(connectorId, stepName, groupName, propertyName, filter);
+
+ final List<AllowableValueEntity> allowableValueEntities = allowableValues.stream()
+ .map(av -> entityFactory.createAllowableValueEntity(dtoFactory.createAllowableValueDto(av), true))
+ .collect(Collectors.toList());
+
+ return entityFactory.createConnectorPropertyAllowableValuesEntity(stepName, groupName, propertyName, allowableValueEntities);
+ }
+
@Override
public ReportingTaskEntity updateReportingTask(final Revision revision, final ReportingTaskDTO reportingTaskDTO) {
// get the component, ensure we have access to it, and perform the update request
@@ -3974,6 +4229,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
return controllerFacade.getFlowFileProcessorTypes(bundleGroup, bundleArtifact, type);
}
+ @Override
+ public Set<DocumentedTypeDTO> getConnectorTypes(final String bundleGroup, final String bundleArtifact, final String type) {
+ return controllerFacade.getConnectorTypes(bundleGroup, bundleArtifact, type);
+ }
+
@Override
public Set<DocumentedTypeDTO> getControllerServiceTypes(final String serviceType, final String serviceBundleGroup, final String serviceBundleArtifact, final String serviceBundleVersion,
final String bundleGroup, final String bundleArtifact, final String type) {
@@ -4952,6 +5212,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
entity.setPoliciesPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getPolicies()));
entity.setSystemPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getSystem()));
entity.setParameterContextPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getParameterContexts()));
+ entity.setConnectorsPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getConnectors()));
entity.setCanVersionFlows(CollectionUtils.isNotEmpty(flowRegistryDAO.getFlowRegistryClients().stream().map(c -> c.getIdentifier()).collect(Collectors.toSet())));
entity.setRestrictedComponentsPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getRestrictedComponents()));
@@ -4978,12 +5239,15 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
@Override
public ProcessGroupFlowEntity getProcessGroupFlow(final String groupId, final boolean uiOnly) {
final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId);
+ return createProcessGroupFlowEntity(processGroup, uiOnly);
+ }
+ private ProcessGroupFlowEntity createProcessGroupFlowEntity(final ProcessGroup processGroup, final boolean uiOnly) {
// Get the Process Group Status but we only need a status depth of one because for any child process group,
// we ignore the status of each individual components. I.e., if Process Group A has child Group B, and child Group B
// has a Processor, we don't care about the individual stats of that Processor because the ProcessGroupFlowEntity
// doesn't include that anyway. So we can avoid including the information in the status that is returned.
- final ProcessGroupStatus groupStatus = controllerFacade.getProcessGroupStatus(groupId, 1);
+ final ProcessGroupStatus groupStatus = controllerFacade.getProcessGroupStatus(processGroup.getIdentifier(), 1);
final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(processGroup.getIdentifier()));
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processGroup);
return entityFactory.createProcessGroupFlowEntity(dtoFactory.createProcessGroupFlowDto(processGroup, groupStatus,
@@ -7350,6 +7614,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
this.reportingTaskDAO = reportingTaskDAO;
}
+ @Autowired
+ public void setConnectorDAO(final ConnectorDAO connectorDAO) {
+ this.connectorDAO = connectorDAO;
+ }
+
@Autowired
public void setFlowAnalysisRuleDAO(FlowAnalysisRuleDAO flowAnalysisRuleDAO) {
this.flowAnalysisRuleDAO = flowAnalysisRuleDAO;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
new file mode 100644
index 0000000000..3c95ad6aa5
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
@@ -0,0 +1,1354 @@
+/*
+ * 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.web.api;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.ServletContext;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.controller.ScheduledState;
+import org.apache.nifi.ui.extension.UiExtension;
+import org.apache.nifi.ui.extension.UiExtensionMapping;
+import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.Revision;
+import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.BundleDTO;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
+import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity;
+import org.apache.nifi.web.api.entity.SearchResultsEntity;
+import org.apache.nifi.web.api.concurrent.AsyncRequestManager;
+import org.apache.nifi.web.api.concurrent.AsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.RequestManager;
+import org.apache.nifi.web.api.concurrent.StandardAsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.StandardUpdateStep;
+import org.apache.nifi.web.api.concurrent.UpdateStep;
+import org.apache.nifi.web.api.dto.VerifyConnectorConfigStepRequestDTO;
+import org.apache.nifi.web.api.entity.VerifyConnectorConfigStepRequestEntity;
+import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.api.request.LongParameter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * RESTful endpoint for managing a Connector.
+ */
+@Controller
+@Path("/connectors")
+@Tag(name = "Connectors")
+public class ConnectorResource extends ApplicationResource {
+
+ private static final Logger logger = LoggerFactory.getLogger(ConnectorResource.class);
+ private static final String VERIFICATION_REQUEST_TYPE = "verification-request";
+
+ private NiFiServiceFacade serviceFacade;
+ private Authorizer authorizer;
+ private FlowResource flowResource;
+ private final RequestManager<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>> configVerificationRequestManager =
+ new AsyncRequestManager<>(100, 1L, "Connector Configuration Step Verification");
+
+ @Context
+ private ServletContext servletContext;
+
+ /**
+ * Populate the uri's for the specified connectors.
+ *
+ * @param connectorEntities connectors
+ * @return connector entities
+ */
+ public Set<ConnectorEntity> populateRemainingConnectorEntitiesContent(final Set<ConnectorEntity> connectorEntities) {
+ for (ConnectorEntity connectorEntity : connectorEntities) {
+ populateRemainingConnectorEntityContent(connectorEntity);
+ }
+ return connectorEntities;
+ }
+
+ /**
+ * Populate the uri's for the specified connector.
+ *
+ * @param connectorEntity connector
+ * @return connector entity
+ */
+ public ConnectorEntity populateRemainingConnectorEntityContent(final ConnectorEntity connectorEntity) {
+ connectorEntity.setUri(generateResourceUri("connectors", connectorEntity.getId()));
+
+ // populate the remaining content
+ if (connectorEntity.getComponent() != null) {
+ populateRemainingConnectorContent(connectorEntity.getComponent());
+ }
+ return connectorEntity;
+ }
+
+ /**
+ * Populates the uri for the specified connector including custom UI information.
+ */
+ public ConnectorDTO populateRemainingConnectorContent(final ConnectorDTO connector) {
+ final BundleDTO bundle = connector.getBundle();
+ if (bundle == null) {
+ return connector;
+ }
+
+ // see if this connector has any ui extensions
+ final UiExtensionMapping uiExtensionMapping = (UiExtensionMapping) servletContext.getAttribute("nifi-ui-extensions");
+ if (uiExtensionMapping.hasUiExtension(connector.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion())) {
+ final List<UiExtension> uiExtensions = uiExtensionMapping.getUiExtension(connector.getType(), bundle.getGroup(), bundle.getArtifact(), bundle.getVersion());
+ for (final UiExtension uiExtension : uiExtensions) {
+ if (UiExtensionType.Connector.equals(uiExtension.getExtensionType())) {
+ final String contextPath = uiExtension.getContextPath();
+ final Map<String, String> routes = uiExtension.getSupportedRoutes();
+
+ if (routes != null) {
+ final String configurationPath = routes.get("configuration");
+ if (configurationPath != null) {
+ connector.setConfigurationUrl(buildCustomUiUrl(contextPath, configurationPath));
+ }
+
+ final String detailsPath = routes.get("details");
+ if (detailsPath != null) {
+ connector.setDetailsUrl(buildCustomUiUrl(contextPath, detailsPath));
+ }
+ }
+ }
+ }
+ }
+
+ return connector;
+ }
+
+ /**
+ * Builds a custom UI URL from the context path and route path.
+ * Handles both hash-based routing (path starts with #) and location-based routing.
+ *
+ * @param contextPath the context path of the custom UI
+ * @param routePath the route path (e.g., "#/wizard" for hash-based or "/wizard" for location-based)
+ * @return the full URL for the custom UI route
+ */
+ private String buildCustomUiUrl(final String contextPath, final String routePath) {
+ final String baseUrl = generateExternalUiUri(contextPath);
+ if (routePath.startsWith("#")) {
+ // Hash-based routing: /context-path/#/route
+ return baseUrl + "/" + routePath;
+ } else {
+ // Location-based routing: /context-path/route
+ return baseUrl + routePath;
+ }
+ }
+
+ /**
+ * Creates a new connector.
+ *
+ * @param requestConnectorEntity A connectorEntity.
+ * @return A connectorEntity.
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Operation(
+ summary = "Creates a new connector",
+ responses = {
+ @ApiResponse(responseCode = "201", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Write - /connectors")
+ }
+ )
+ public Response createConnector(
+ @Parameter(description = "The connector configuration details.", required = true) final ConnectorEntity requestConnectorEntity) {
+
+ if (requestConnectorEntity == null || requestConnectorEntity.getComponent() == null) {
+ throw new IllegalArgumentException("Connector details must be specified.");
+ }
+
+ if (requestConnectorEntity.getRevision() == null || (requestConnectorEntity.getRevision().getVersion() == null || requestConnectorEntity.getRevision().getVersion() != 0)) {
+ throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Connector.");
+ }
+
+ final ConnectorDTO requestConnector = requestConnectorEntity.getComponent();
+ if (requestConnector.getId() != null) {
+ throw new IllegalArgumentException("Connector ID cannot be specified.");
+ }
+
+ if (StringUtils.isBlank(requestConnector.getType())) {
+ throw new IllegalArgumentException("The type of connector to create must be specified.");
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.POST, requestConnectorEntity);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(requestConnectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ return withWriteLock(
+ serviceFacade,
+ requestConnectorEntity,
+ lookup -> {
+ final Authorizable connectors = lookup.getConnectors();
+ connectors.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+ },
+ () -> serviceFacade.verifyCreateConnector(requestConnector),
+ connectorEntity -> {
+ final ConnectorDTO connector = connectorEntity.getComponent();
+
+ // set the connector id as appropriate
+ connector.setId(generateUuid());
+
+ // create the new connector
+ final Revision revision = getRevision(connectorEntity, connector.getId());
+ final ConnectorEntity entity = serviceFacade.createConnector(revision, connector);
+ populateRemainingConnectorEntityContent(entity);
+
+ // generate a 201 created response
+ final String uri = entity.getUri();
+ return generateCreatedResponse(URI.create(uri), entity).build();
+ }
+ );
+ }
+
+ /**
+ * Retrieves the specified connector.
+ *
+ * @param id The id of the connector to retrieve
+ * @return A connectorEntity.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}")
+ @Operation(
+ summary = "Gets a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ }
+ )
+ public Response getConnector(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // authorize access
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ // get the connector
+ final ConnectorEntity entity = serviceFacade.getConnector(id);
+ populateRemainingConnectorEntityContent(entity);
+
+ return generateOkResponse(entity).build();
+ }
+
+ /**
+ * Updates the specified connector.
+ *
+ * @param id The id of the connector to update.
+ * @param requestConnectorEntity A connectorEntity.
+ * @return A connectorEntity.
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}")
+ @Operation(
+ summary = "Updates a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid}")
+ }
+ )
+ public Response updateConnector(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "The connector configuration details.",
+ required = true
+ ) final ConnectorEntity requestConnectorEntity) {
+
+ if (requestConnectorEntity == null || requestConnectorEntity.getComponent() == null) {
+ throw new IllegalArgumentException("Connector details must be specified.");
+ }
+
+ if (requestConnectorEntity.getRevision() == null) {
+ throw new IllegalArgumentException("Revision must be specified.");
+ }
+
+ // ensure the ids are the same
+ final ConnectorDTO requestConnectorDTO = requestConnectorEntity.getComponent();
+ if (!id.equals(requestConnectorDTO.getId())) {
+ throw new IllegalArgumentException(String.format("The connector id (%s) in the request body does not equal the "
+ + "connector id of the requested resource (%s).", requestConnectorDTO.getId(), id));
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.PUT, requestConnectorEntity);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(requestConnectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ // handle expects request (usually from the cluster manager)
+ final Revision requestRevision = getRevision(requestConnectorEntity, id);
+ return withWriteLock(
+ serviceFacade,
+ requestConnectorEntity,
+ requestRevision,
+ lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+ },
+ () -> serviceFacade.verifyUpdateConnector(requestConnectorDTO),
+ (revision, connectorEntity) -> {
+ final ConnectorDTO connectorDTO = connectorEntity.getComponent();
+
+ // update the connector
+ final ConnectorEntity entity = serviceFacade.updateConnector(revision, connectorDTO);
+ populateRemainingConnectorEntityContent(entity);
+
+ return generateOkResponse(entity).build();
+ }
+ );
+ }
+
+ /**
+ * Removes the specified connector.
+ *
+ * @param version The revision is used to verify the client is working with the latest version of the flow.
+ * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response.
+ * @param id The id of the connector to remove.
+ * @return A connectorEntity.
+ */
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}")
+ @Operation(
+ summary = "Deletes a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid}")
+ }
+ )
+ public Response deleteConnector(
+ @Parameter(
+ description = "The revision is used to verify the client is working with the latest version of the flow."
+ )
+ @QueryParam(VERSION) final LongParameter version,
+ @Parameter(
+ description = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response."
+ )
+ @QueryParam(CLIENT_ID) final ClientIdParameter clientId,
+ @Parameter(
+ description = "Acknowledges that this node is disconnected to allow for mutable requests to proceed."
+ )
+ @QueryParam(DISCONNECTED_NODE_ACKNOWLEDGED) @DefaultValue("false") final Boolean disconnectedNodeAcknowledged,
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.DELETE);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(disconnectedNodeAcknowledged);
+ }
+
+ final ConnectorEntity requestConnectorEntity = new ConnectorEntity();
+ requestConnectorEntity.setId(id);
+
+ // handle expects request (usually from the cluster manager)
+ final Revision requestRevision = new Revision(version == null ? null : version.getLong(), clientId.getClientId(), id);
+ return withWriteLock(
+ serviceFacade,
+ requestConnectorEntity,
+ requestRevision,
+ lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+ },
+ () -> serviceFacade.verifyDeleteConnector(id),
+ (revision, connectorEntity) -> {
+ // delete the specified connector
+ final ConnectorEntity entity = serviceFacade.deleteConnector(revision, connectorEntity.getId());
+ return generateOkResponse(entity).build();
+ }
+ );
+ }
+
+ /**
+ * Updates run status of a connector.
+ *
+ * @param id The id of the connector to update.
+ * @param requestRunStatus A connectorRunStatusEntity.
+ * @return A connectorEntity.
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/run-status")
+ @Operation(
+ summary = "Updates run status of a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid} or /operation/connectors/{uuid}")
+ }
+ )
+ public Response updateRunStatus(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "The connector run status.",
+ required = true
+ ) final ConnectorRunStatusEntity requestRunStatus) {
+
+ if (requestRunStatus == null) {
+ throw new IllegalArgumentException("Connector run status must be specified.");
+ }
+
+ if (requestRunStatus.getRevision() == null) {
+ throw new IllegalArgumentException("Revision must be specified.");
+ }
+
+ requestRunStatus.validateState();
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.PUT, requestRunStatus);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(requestRunStatus.isDisconnectedNodeAcknowledged());
+ }
+
+ // handle expects request (usually from the cluster manager)
+ final Revision requestRevision = getRevision(requestRunStatus.getRevision(), id);
+ return withWriteLock(
+ serviceFacade,
+ requestRunStatus,
+ requestRevision,
+ lookup -> {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ final Authorizable connector = lookup.getConnector(id);
+ OperationAuthorizable.authorizeOperation(connector, authorizer, user);
+ },
+ () -> serviceFacade.verifyUpdateConnector(createDTOWithDesiredRunStatus(id, requestRunStatus.getState())),
+ (revision, runStatusEntity) -> {
+ // update the connector
+ final ScheduledState scheduledState = ScheduledState.valueOf(runStatusEntity.getState());
+ final ConnectorEntity entity = serviceFacade.scheduleConnector(revision, id, scheduledState);
+ populateRemainingConnectorEntityContent(entity);
+
+ return generateOkResponse(entity).build();
+ }
+ );
+ }
+
+ /**
+ * Gets the configuration step names for the specified connector.
+ *
+ * @param id The id of the connector to retrieve configuration steps from
+ * @return A configurationStepNamesEntity.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/configuration-steps")
+ @Operation(
+ summary = "Gets all configuration step names for a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConfigurationStepNamesEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ }
+ )
+ public Response getConnectorConfigurationSteps(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // authorize access
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ // get the configuration step names
+ final ConfigurationStepNamesEntity entity = serviceFacade.getConnectorConfigurationSteps(id);
+
+ return generateOkResponse(entity).build();
+ }
+
+ /**
+ * Gets a specific configuration step by name for the specified connector.
+ *
+ * @param id The id of the connector to retrieve configuration step from
+ * @param configurationStepName The name of the configuration step to retrieve
+ * @return A configurationStepEntity.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/configuration-steps/{configurationStepName}")
+ @Operation(
+ summary = "Gets a specific configuration step by name for a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConfigurationStepEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ }
+ )
+ public Response getConnectorConfigurationStep(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "The configuration step name.",
+ required = true
+ )
+ @PathParam("configurationStepName") final String configurationStepName) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // authorize access
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ // get the specific configuration step
+ final ConfigurationStepEntity entity = serviceFacade.getConnectorConfigurationStep(id, configurationStepName);
+
+ return generateOkResponse(entity).build();
+ }
+
+ /**
+ * Gets the allowable values for a specific property in a connector's configuration step.
+ *
+ * @param id The id of the connector
+ * @param configurationStepName The name of the configuration step
+ * @param propertyGroupName The name of the property group
+ * @param propertyName The name of the property
+ * @param filter Optional filter for the allowable values
+ * @return A ConnectorPropertyAllowableValuesEntity
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/configuration-steps/{configurationStepName}/property-groups/{propertyGroupName}/properties/{propertyName}/allowable-values")
+ @Operation(
+ summary = "Gets the allowable values for a specific property in a connector's configuration step",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorPropertyAllowableValuesEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "Gets the allowable values for a specific property that supports dynamic fetching of allowable values. " +
+ "The filter parameter can be used to narrow down the results based on the property's filtering logic.",
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ }
+ )
+ public Response getConnectorPropertyAllowableValues(
+ @Parameter(description = "The connector id.", required = true)
+ @PathParam("id") final String id,
+ @Parameter(description = "The configuration step name.", required = true)
+ @PathParam("configurationStepName") final String configurationStepName,
+ @Parameter(description = "The property group name.", required = true)
+ @PathParam("propertyGroupName") final String propertyGroupName,
+ @Parameter(description = "The property name.", required = true)
+ @PathParam("propertyName") final String propertyName,
+ @Parameter(description = "Optional filter to narrow down the allowable values.")
+ @QueryParam("filter") final String filter) {
+
+ // NOTE: fetching allowable values is handled by the node that receives the request and does not need to be replicated
+
+ // authorize access
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ // get the allowable values
+ final ConnectorPropertyAllowableValuesEntity entity = serviceFacade.getConnectorPropertyAllowableValues(id, configurationStepName, propertyGroupName, propertyName, filter);
+
+ return generateOkResponse(entity).build();
+ }
+
+ /**
+ * Updates a specific configuration step by name for the specified connector.
+ *
+ * @param id The id of the connector to update configuration step for
+ * @param configurationStepName The name of the configuration step to update
+ * @param requestConfigurationStepEntity The configuration step configuration to update
+ * @return A configurationStepEntity.
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/configuration-steps/{configurationStepName}")
+ @Operation(
+ summary = "Updates a specific configuration step by name for a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConfigurationStepEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid}")
+ }
+ )
+ public Response updateConnectorConfigurationStep(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "The configuration step name.",
+ required = true
+ )
+ @PathParam("configurationStepName") final String configurationStepName,
+ @Parameter(
+ description = "The configuration step configuration.",
+ required = true
+ ) final ConfigurationStepEntity requestConfigurationStepEntity) {
+
+ if (requestConfigurationStepEntity == null || requestConfigurationStepEntity.getConfigurationStep() == null) {
+ throw new IllegalArgumentException("Configuration step details must be specified.");
+ }
+
+ if (requestConfigurationStepEntity.getParentConnectorRevision() == null) {
+ throw new IllegalArgumentException("Parent connector revision must be specified.");
+ }
+
+ if (requestConfigurationStepEntity.getParentConnectorId() == null) {
+ throw new IllegalArgumentException("Parent connector ID must be specified.");
+ }
+
+ if (!id.equals(requestConfigurationStepEntity.getParentConnectorId())) {
+ throw new IllegalArgumentException(String.format("The parent connector ID (%s) in the request body does not equal the "
+ + "connector ID of the requested resource (%s).", requestConfigurationStepEntity.getParentConnectorId(), id));
+ }
+
+ // ensure the configuration step names match
+ final ConfigurationStepConfigurationDTO requestConfigurationStep = requestConfigurationStepEntity.getConfigurationStep();
+ if (!configurationStepName.equals(requestConfigurationStep.getConfigurationStepName())) {
+ throw new IllegalArgumentException(String.format("The configuration step name (%s) in the request body does not equal the "
+ + "configuration step name of the requested resource (%s).", requestConfigurationStep.getConfigurationStepName(), configurationStepName));
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.PUT, requestConfigurationStepEntity);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(requestConfigurationStepEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ // handle expects request (usually from the cluster manager)
+ final Revision requestRevision = getRevision(requestConfigurationStepEntity.getParentConnectorRevision(), id);
+ return withWriteLock(
+ serviceFacade,
+ requestConfigurationStepEntity,
+ requestRevision,
+ lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+ },
+ () -> {
+ // Verify the connector exists and the configuration step exists
+ serviceFacade.getConnectorConfigurationStep(id, configurationStepName);
+ },
+ (revision, configurationStepEntity) -> {
+ final ConfigurationStepConfigurationDTO configurationStepConfiguration = configurationStepEntity.getConfigurationStep();
+
+ // update the configuration step
+ final ConfigurationStepEntity entity = serviceFacade.updateConnectorConfigurationStep(revision, id, configurationStepName, configurationStepConfiguration);
+
+ return generateOkResponse(entity).build();
+ }
+ );
+ }
+
+ /**
+ * Submits a request to perform verification of a specific configuration step for a connector.
+ * This is an asynchronous operation that will return immediately with a request ID that can be
+ * used to poll for the results.
+ *
+ * @param id The id of the connector
+ * @param configurationStepName The name of the configuration step to verify
+ * @param requestEntity The verify config request entity containing the properties to verify
+ * @return The verification request entity with the request ID
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/configuration-steps/{configurationStepName}/verify-config")
+ @Operation(
+ summary = "Performs verification of a configuration step for a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = VerifyConnectorConfigStepRequestEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "This will initiate the process of verifying a given Connector Configuration Step. This may be a long-running task. As a result, " +
+ "this endpoint will immediately return a VerifyConnectorConfigStepRequestEntity, and the process of performing the verification will occur asynchronously in the background. " +
+ "The client may then periodically poll the status of the request by issuing a GET request to " +
+ "/connectors/{connectorId}/configuration-steps/{stepName}/verify-config/{requestId}. Once the request is completed, the client is expected to issue a DELETE request to " +
+ "/connectors/{connectorId}/configuration-steps/{stepName}/verify-config/{requestId}.",
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid}")
+ }
+ )
+ public Response submitConfigurationStepVerificationRequest(
+ @Parameter(description = "The connector id.", required = true)
+ @PathParam("id") final String id,
+ @Parameter(description = "The configuration step name.", required = true)
+ @PathParam("configurationStepName") final String configurationStepName,
+ @Parameter(description = "The verify config request entity containing the configuration step to verify.", required = true)
+ final VerifyConnectorConfigStepRequestEntity requestEntity) {
+
+ if (requestEntity == null) {
+ throw new IllegalArgumentException("Connector configuration step verification request must be specified.");
+ }
+
+ final VerifyConnectorConfigStepRequestDTO requestDto = requestEntity.getRequest();
+ if (requestDto == null || requestDto.getConfigurationStep() == null) {
+ throw new IllegalArgumentException("Connector configuration step must be specified.");
+ }
+
+ if (requestDto.getConnectorId() == null) {
+ throw new IllegalArgumentException("Connector's identifier must be specified in the request.");
+ }
+
+ if (!requestDto.getConnectorId().equals(id)) {
+ throw new IllegalArgumentException("Connector's identifier in the request must match the identifier provided in the URL.");
+ }
+
+ if (requestDto.getConfigurationStepName() == null) {
+ throw new IllegalArgumentException("Configuration step name must be specified in the request.");
+ }
+
+ if (!requestDto.getConfigurationStepName().equals(configurationStepName)) {
+ throw new IllegalArgumentException("Configuration step name in the request must match the step name provided in the URL.");
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.POST, requestEntity);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ return withWriteLock(
+ serviceFacade,
+ requestEntity,
+ lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+ },
+ () -> {
+ serviceFacade.verifyCanVerifyConnectorConfigurationStep(id, configurationStepName);
+ },
+ entity -> performAsyncConfigStepVerification(entity, id, configurationStepName, user)
+ );
+ }
+
+ /**
+ * Returns the verification request with the given ID for a connector configuration step.
+ *
+ * @param id The id of the connector
+ * @param configurationStepName The name of the configuration step
+ * @param requestId The id of the verification request
+ * @return The verification request entity
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/configuration-steps/{configurationStepName}/verify-config/{requestId}")
+ @Operation(
+ summary = "Returns the Verification Request with the given ID",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = VerifyConnectorConfigStepRequestEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "Returns the Verification Request with the given ID. Once a Verification Request has been created, "
+ + "that request can subsequently be retrieved via this endpoint, and the request that is fetched will contain the updated state, such as percent complete, the "
+ + "current state of the request, and any failures.",
+ security = {
+ @SecurityRequirement(name = "Only the user that submitted the request can get it")
+ }
+ )
+ public Response getConfigurationStepVerificationRequest(
+ @Parameter(description = "The connector id.", required = true)
+ @PathParam("id") final String id,
+ @Parameter(description = "The configuration step name.", required = true)
+ @PathParam("configurationStepName") final String configurationStepName,
+ @Parameter(description = "The ID of the Verification Request", required = true)
+ @PathParam("requestId") final String requestId) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ final AsynchronousWebRequest<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest =
+ configVerificationRequestManager.getRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+ final VerifyConnectorConfigStepRequestEntity updateRequestEntity = createVerifyConnectorConfigStepRequestEntity(asyncRequest, id, configurationStepName, requestId);
+ return generateOkResponse(updateRequestEntity).build();
+ }
+
+ /**
+ * Deletes the verification request with the given ID for a connector configuration step.
+ *
+ * @param id The id of the connector
+ * @param configurationStepName The name of the configuration step
+ * @param requestId The id of the verification request
+ * @return The verification request entity
+ */
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/configuration-steps/{configurationStepName}/verify-config/{requestId}")
+ @Operation(
+ summary = "Deletes the Verification Request with the given ID",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = VerifyConnectorConfigStepRequestEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "Deletes the Verification Request with the given ID. After a request is created, it is expected "
+ + "that the client will properly clean up the request by DELETE'ing it, once the Verification process has completed. If the request is deleted before the request "
+ + "completes, then the Verification request will finish the step that it is currently performing and then will cancel any subsequent steps.",
+ security = {
+ @SecurityRequirement(name = "Only the user that submitted the request can remove it")
+ }
+ )
+ public Response deleteConfigurationStepVerificationRequest(
+ @Parameter(description = "The connector id.", required = true)
+ @PathParam("id") final String id,
+ @Parameter(description = "The configuration step name.", required = true)
+ @PathParam("configurationStepName") final String configurationStepName,
+ @Parameter(description = "The ID of the Verification Request", required = true)
+ @PathParam("requestId") final String requestId) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.DELETE);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final boolean twoPhaseRequest = isTwoPhaseRequest(httpServletRequest);
+ final boolean executionPhase = isExecutionPhase(httpServletRequest);
+
+ if (!twoPhaseRequest || executionPhase) {
+ final AsynchronousWebRequest<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest =
+ configVerificationRequestManager.removeRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+
+ if (!asyncRequest.isComplete()) {
+ asyncRequest.cancel();
+ }
+
+ final VerifyConnectorConfigStepRequestEntity updateRequestEntity = createVerifyConnectorConfigStepRequestEntity(asyncRequest, id, configurationStepName, requestId);
+ return generateOkResponse(updateRequestEntity).build();
+ }
+
+ if (isValidationPhase(httpServletRequest)) {
+ configVerificationRequestManager.getRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+ return generateContinueResponse().build();
+ } else if (isCancellationPhase(httpServletRequest)) {
+ return generateOkResponse().build();
+ } else {
+ throw new IllegalStateException("This request does not appear to be part of the two phase commit.");
+ }
+ }
+
+ private Response performAsyncConfigStepVerification(final VerifyConnectorConfigStepRequestEntity requestEntity, final String connectorId,
+ final String configurationStepName, final NiFiUser user) {
+ final String requestId = generateUuid();
+ logger.debug("Generated Config Verification Request with ID {} for Connector {} Configuration Step {}", requestId, connectorId, configurationStepName);
+
+ final VerifyConnectorConfigStepRequestDTO requestDto = requestEntity.getRequest();
+ final List<UpdateStep> updateSteps = Collections.singletonList(new StandardUpdateStep("Verify Connector Configuration Step"));
+
+ final AsynchronousWebRequest<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>> request =
+ new StandardAsynchronousWebRequest<>(requestId, requestEntity, connectorId, user, updateSteps);
+
+ final Consumer<AsynchronousWebRequest<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>>> updateTask = asyncRequest -> {
+ try {
+ final List<PropertyGroupConfigurationDTO> propertyGroupConfigurations = requestDto.getConfigurationStep().getPropertyGroupConfigurations();
+ final List<ConfigVerificationResultDTO> results = serviceFacade.performConnectorConfigurationStepVerification(connectorId, configurationStepName, propertyGroupConfigurations);
+ asyncRequest.markStepComplete(results);
+ } catch (final Exception e) {
+ logger.error("Failed to verify Connector Configuration Step", e);
+ asyncRequest.fail("Failed to verify Connector Configuration Step due to " + e);
+ }
+ };
+
+ configVerificationRequestManager.submitRequest(VERIFICATION_REQUEST_TYPE, requestId, request, updateTask);
+
+ final VerifyConnectorConfigStepRequestEntity resultsEntity = createVerifyConnectorConfigStepRequestEntity(request, connectorId, configurationStepName, requestId);
+ return generateOkResponse(resultsEntity).build();
+ }
+
+ private VerifyConnectorConfigStepRequestEntity createVerifyConnectorConfigStepRequestEntity(
+ final AsynchronousWebRequest<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest,
+ final String connectorId, final String configurationStepName, final String requestId) {
+
+ final VerifyConnectorConfigStepRequestDTO requestDto = asyncRequest.getRequest().getRequest();
+ final List<ConfigVerificationResultDTO> resultsList = asyncRequest.getResults();
+
+ final VerifyConnectorConfigStepRequestDTO dto = new VerifyConnectorConfigStepRequestDTO();
+ dto.setConnectorId(requestDto.getConnectorId());
+ dto.setConfigurationStepName(requestDto.getConfigurationStepName());
+ dto.setConfigurationStep(requestDto.getConfigurationStep());
+ dto.setResults(resultsList);
+
+ dto.setComplete(asyncRequest.isComplete());
+ dto.setFailureReason(asyncRequest.getFailureReason());
+ dto.setLastUpdated(asyncRequest.getLastUpdated());
+ dto.setPercentCompleted(asyncRequest.getPercentComplete());
+ dto.setRequestId(requestId);
+ dto.setState(asyncRequest.getState());
+ dto.setUri(generateResourceUri("connectors", connectorId, "configuration-steps", configurationStepName, "verify-config", requestId));
+
+ final VerifyConnectorConfigStepRequestEntity entity = new VerifyConnectorConfigStepRequestEntity();
+ entity.setRequest(dto);
+ return entity;
+ }
+
+ /**
+ * Performs a search request within the encapsulated process group of this connector.
+ *
+ * @param id The connector id
+ * @param value Search string
+ * @return A searchResultsEntity
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/search-results")
+ @Operation(
+ summary = "Performs a search against the encapsulated process group of this connector using the specified search term",
+ description = "Only search results from authorized components will be returned.",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = SearchResultsEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ }
+ )
+ public Response searchConnector(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "The search term.",
+ required = false
+ )
+ @QueryParam("q") @DefaultValue(StringUtils.EMPTY) final String value
+ ) {
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // authorize access to the connector
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ // search within the connector's encapsulated process group
+ final SearchResultsDTO results = serviceFacade.searchConnector(id, value);
+
+ // create the entity
+ final SearchResultsEntity entity = new SearchResultsEntity();
+ entity.setSearchResultsDTO(results);
+
+ // generate the response
+ return noCache(Response.ok(entity)).build();
+ }
+
+ private ConnectorDTO createDTOWithDesiredRunStatus(final String id, final String runStatus) {
+ final ConnectorDTO dto = new ConnectorDTO();
+ dto.setId(id);
+ dto.setState(runStatus);
+ return dto;
+ }
+
+ /**
+ * Applies an update to a connector.
+ *
+ * @param id The id of the connector to apply update to.
+ * @param requestConnectorEntity A connectorEntity containing the revision.
+ * @return A connectorEntity.
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/apply-update")
+ @Operation(
+ summary = "Applies an update to a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ description = "This will apply any pending configuration changes to the connector. The client can poll the connector endpoint to check when the update is complete.",
+ security = {
+ @SecurityRequirement(name = "Write - /connectors/{uuid}")
+ }
+ )
+ public Response applyConnectorUpdate(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "The connector configuration with revision.",
+ required = true
+ ) final ConnectorEntity requestConnectorEntity) {
+
+ if (requestConnectorEntity == null || requestConnectorEntity.getRevision() == null) {
+ throw new IllegalArgumentException("Connector entity with revision must be specified.");
+ }
+
+ // ensure the ids are the same
+ if (requestConnectorEntity.getId() != null && !id.equals(requestConnectorEntity.getId())) {
+ throw new IllegalArgumentException(String.format("The connector id (%s) in the request body does not equal the "
+ + "connector id of the requested resource (%s).", requestConnectorEntity.getId(), id));
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.POST, requestConnectorEntity);
+ } else if (isDisconnectedFromCluster()) {
+ verifyDisconnectedNodeModification(requestConnectorEntity.isDisconnectedNodeAcknowledged());
+ }
+
+ // handle expects request (usually from the cluster manager)
+ final Revision requestRevision = getRevision(requestConnectorEntity, id);
+ return withWriteLock(
+ serviceFacade,
+ requestConnectorEntity,
+ requestRevision,
+ lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+ },
+ () -> {
+ // Verify the connector exists
+ serviceFacade.getConnector(id);
+ },
+ (revision, connectorEntity) -> {
+ // apply the connector update
+ final ConnectorEntity entity = serviceFacade.applyConnectorUpdate(revision, id);
+ populateRemainingConnectorEntityContent(entity);
+
+ return generateOkResponse(entity).build();
+ }
+ );
+ }
+
+ /**
+ * Retrieves the flow for the process group managed by the specified connector.
+ *
+ * @param id The id of the connector
+ * @param uiOnly Whether to return only UI-specific fields
+ * @return A processGroupFlowEntity
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/flow")
+ @Operation(
+ summary = "Gets the flow for the process group managed by a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ProcessGroupFlowEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ },
+ description = "Returns the flow for the process group managed by the specified connector. If the uiOnly query parameter is " +
+ "provided with a value of true, the returned entity may only contain fields that are necessary for rendering the NiFi User " +
+ "Interface. As such, the selected fields may change at any time, even during incremental releases, without warning. " +
+ "As a result, this parameter should not be provided by any client other than the UI."
+ )
+ public Response getFlow(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "Whether to return only UI-specific fields"
+ )
+ @QueryParam("uiOnly") @DefaultValue("false") final boolean uiOnly) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // authorize access to the connector
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ // get the flow for the connector's managed process group
+ final ProcessGroupFlowEntity entity = serviceFacade.getConnectorFlow(id, uiOnly);
+ flowResource.populateRemainingFlowContent(entity.getProcessGroupFlow());
+ return generateOkResponse(entity).build();
+ }
+
+ /**
+ * Retrieves the status for the process group managed by the specified connector.
+ *
+ * @param id The id of the connector
+ * @param recursive Optional recursive flag that defaults to false. If set to true, all descendant groups and the status of their content will be included.
+ * @param nodewise Whether to include breakdown per node
+ * @param clusterNodeId The id of a specific node to get status from
+ * @return A processGroupStatusEntity
+ * @throws InterruptedException if interrupted
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/status")
+ @Operation(
+ summary = "Gets the status for the process group managed by a connector",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ProcessGroupStatusEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "404", description = "The specified resource could not be found."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /connectors/{uuid}")
+ },
+ description = "Returns the status for the process group managed by the specified connector. The status includes status for all descendent components. " +
+ "When invoked with recursive set to true, it will return the current status of every component in the connector's encapsulated flow."
+ )
+ public Response getConnectorStatus(
+ @Parameter(
+ description = "The connector id.",
+ required = true
+ )
+ @PathParam("id") final String id,
+ @Parameter(
+ description = "Whether all descendant groups and the status of their content will be included. Optional, defaults to false"
+ )
+ @QueryParam("recursive") @DefaultValue("false") final Boolean recursive,
+ @Parameter(
+ description = "Whether or not to include the breakdown per node. Optional, defaults to false"
+ )
+ @QueryParam("nodewise") @DefaultValue("false") final Boolean nodewise,
+ @Parameter(
+ description = "The id of the node where to get the status."
+ )
+ @QueryParam("clusterNodeId") final String clusterNodeId) throws InterruptedException {
+
+ // ensure a valid request
+ if (Boolean.TRUE.equals(nodewise) && clusterNodeId != null) {
+ throw new IllegalArgumentException("Nodewise requests cannot be directed at a specific node.");
+ }
+
+ if (isReplicateRequest()) {
+ // determine where this request should be sent
+ if (clusterNodeId == null) {
+ final NodeResponse nodeResponse = replicateNodeResponse(HttpMethod.GET);
+ final ProcessGroupStatusEntity entity = (ProcessGroupStatusEntity) nodeResponse.getUpdatedEntity();
+
+ // ensure there is an updated entity (result of merging) and prune the response as necessary
+ if (entity != null && !nodewise) {
+ entity.getProcessGroupStatus().setNodeSnapshots(null);
+ }
+
+ return nodeResponse.getResponse();
+ } else {
+ return replicate(HttpMethod.GET, clusterNodeId);
+ }
+ }
+
+ // authorize access to the connector
+ serviceFacade.authorizeAccess(lookup -> {
+ final Authorizable connector = lookup.getConnector(id);
+ connector.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ });
+
+ // get the status for the connector's managed process group
+ final ProcessGroupStatusEntity entity = serviceFacade.getConnectorProcessGroupStatus(id, recursive);
+ return generateOkResponse(entity).build();
+ }
+
+ // -----------------
+ // setters
+ // -----------------
+
+ @Autowired
+ public void setServiceFacade(final NiFiServiceFacade serviceFacade) {
+ this.serviceFacade = serviceFacade;
+ }
+
+ @Autowired
+ public void setAuthorizer(final Authorizer authorizer) {
+ this.authorizer = authorizer;
+ }
+
+ @Autowired
+ public void setFlowResource(final FlowResource flowResource) {
+ this.flowResource = flowResource;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
index 3a3fa257ec..643ff8dc61 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
@@ -115,6 +115,9 @@ import org.apache.nifi.web.api.entity.ClusterSummaryEntity;
import org.apache.nifi.web.api.entity.ComponentHistoryEntity;
import org.apache.nifi.web.api.entity.ConnectionStatisticsEntity;
import org.apache.nifi.web.api.entity.ConnectionStatusEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorTypesEntity;
+import org.apache.nifi.web.api.entity.ConnectorsEntity;
import org.apache.nifi.web.api.entity.ContentViewerEntity;
import org.apache.nifi.web.api.entity.ControllerBulletinsEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
@@ -231,6 +234,7 @@ public class FlowResource extends ApplicationResource {
private ControllerServiceResource controllerServiceResource;
private ReportingTaskResource reportingTaskResource;
private ParameterProviderResource parameterProviderResource;
+ private ConnectorResource connectorResource;
@Context
private ServletContext servletContext;
@@ -240,7 +244,7 @@ public class FlowResource extends ApplicationResource {
*
* @param flow group
*/
- private void populateRemainingFlowContent(ProcessGroupFlowDTO flow) {
+ public void populateRemainingFlowContent(final ProcessGroupFlowDTO flow) {
FlowDTO flowStructure = flow.getFlow();
// populate the remaining fields for the processors, connections, process group refs, remote process groups, and labels if appropriate
@@ -777,6 +781,105 @@ public class FlowResource extends ApplicationResource {
return generateOkResponse(entity).build();
}
+ // ----------
+ // connectors
+ // ----------
+
+ /**
+ * Retrieves all the of connectors in this NiFi.
+ *
+ * @return A connectorsEntity.
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("connectors")
+ @Operation(
+ summary = "Gets all connectors",
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorsEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /flow")
+ }
+ )
+ public Response getConnectors() {
+
+ authorizeFlow();
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // get all the connectors
+ final Set<ConnectorEntity> connectors = serviceFacade.getConnectors();
+ connectorResource.populateRemainingConnectorEntitiesContent(connectors);
+
+ // create the response entity
+ final ConnectorsEntity entity = new ConnectorsEntity();
+ entity.setCurrentTime(new Date());
+ entity.setConnectors(connectors);
+
+ // generate the response
+ return generateOkResponse(entity).build();
+ }
+
+ /**
+ * Retrieves the types of connectors that this NiFi supports.
+ *
+ * @return A connectorTypesEntity.
+ * @throws InterruptedException if interrupted
+ */
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("connector-types")
+ @Operation(
+ summary = "Retrieves the types of connectors that this NiFi supports",
+ description = NON_GUARANTEED_ENDPOINT,
+ responses = {
+ @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = ConnectorTypesEntity.class))),
+ @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(responseCode = "401", description = "Client could not be authenticated."),
+ @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."),
+ @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.")
+ },
+ security = {
+ @SecurityRequirement(name = "Read - /flow")
+ }
+ )
+ public Response getConnectorTypes(
+ @Parameter(
+ description = "If specified, will only return types that are a member of this bundle group."
+ )
+ @QueryParam("bundleGroupFilter") String bundleGroupFilter,
+ @Parameter(
+ description = "If specified, will only return types that are a member of this bundle artifact."
+ )
+ @QueryParam("bundleArtifactFilter") String bundleArtifactFilter,
+ @Parameter(
+ description = "If specified, will only return types whose fully qualified classname matches."
+ )
+ @QueryParam("type") String typeFilter) throws InterruptedException {
+
+ authorizeFlow();
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ // create response entity
+ final ConnectorTypesEntity entity = new ConnectorTypesEntity();
+ entity.setConnectorTypes(serviceFacade.getConnectorTypes(bundleGroupFilter, bundleArtifactFilter, typeFilter));
+
+ // generate the response
+ return generateOkResponse(entity).build();
+ }
+
// ---------------
// reporting-tasks
// ---------------
@@ -4156,6 +4259,11 @@ public class FlowResource extends ApplicationResource {
this.parameterProviderResource = parameterProviderResource;
}
+ @Autowired
+ public void setConnectorResource(final ConnectorResource connectorResource) {
+ this.connectorResource = connectorResource;
+ }
+
@Autowired
public void setAuthorizer(Authorizer authorizer) {
this.authorizer = authorizer;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index a67a904ff5..9fd2885102 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -62,6 +62,7 @@ import org.apache.nifi.cluster.event.NodeEvent;
import org.apache.nifi.cluster.manager.StatusMerger;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.PropertyDependency;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationResult;
@@ -70,6 +71,18 @@ import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.components.validation.ValidationState;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.components.connector.AssetReference;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConfigurationStepConfiguration;
+import org.apache.nifi.components.connector.ConnectorConfiguration;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.ConnectorValueReference;
+import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.StringLiteralValue;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.connectable.Funnel;
@@ -5217,4 +5230,206 @@ public final class DtoFactory {
final byte[] digest = MessageDigestUtils.getDigest(bytes);
return HexFormat.of().formatHex(digest);
}
+
+ public ConnectorDTO createConnectorDto(final ConnectorNode connector) {
+ if (connector == null) {
+ return null;
+ }
+
+ final ConnectorDTO dto = new ConnectorDTO();
+ dto.setId(connector.getIdentifier());
+ dto.setName(connector.getName());
+
+ final String canonicalName = connector.getConnector().getClass().getCanonicalName();
+ dto.setType(canonicalName != null ? canonicalName : connector.getConnector().getClass().getName());
+
+ dto.setBundle(createBundleDto(connector.getBundleCoordinate()));
+ dto.setState(connector.getCurrentState().name());
+ dto.setActiveConfiguration(createConnectorConfigurationDtoFromFlowContext(connector, connector.getActiveFlowContext()));
+ dto.setWorkingConfiguration(createConnectorConfigurationDtoFromFlowContext(connector, connector.getWorkingFlowContext()));
+
+ return dto;
+ }
+
+ private ConnectorConfigurationDTO createConnectorConfigurationDtoFromFlowContext(final ConnectorNode connector, final FrameworkFlowContext flowContext) {
+ final List<ConfigurationStep> configurationSteps = connector.getConfigurationSteps();
+ if (configurationSteps == null || configurationSteps.isEmpty()) {
+ return null;
+ }
+
+ if (flowContext == null || flowContext.getConfigurationContext() == null) {
+ return null;
+ }
+
+ final ConnectorConfiguration configuration = flowContext.getConfigurationContext().toConnectorConfiguration();
+ final ConnectorConfigurationDTO dto = new ConnectorConfigurationDTO();
+ final List<ConfigurationStepConfigurationDTO> configurationStepDtos = configurationSteps.stream()
+ .map(step -> createConfigurationStepConfigurationDtoFromStep(step, configuration))
+ .collect(Collectors.toList());
+ dto.setConfigurationStepConfigurations(configurationStepDtos);
+ return dto;
+ }
+
+ private ConfigurationStepConfigurationDTO createConfigurationStepConfigurationDtoFromStep(final ConfigurationStep step, final ConnectorConfiguration configuration) {
+ if (step == null) {
+ return null;
+ }
+
+ final ConfigurationStepConfigurationDTO dto = new ConfigurationStepConfigurationDTO();
+ dto.setConfigurationStepName(step.getName());
+ dto.setConfigurationStepDescription(step.getDescription());
+
+ // Get the current configuration values for this step
+ final ConfigurationStepConfiguration stepConfig = configuration.getConfigurationStepConfigurations().stream()
+ .filter(c -> step.getName().equals(c.stepName()))
+ .findFirst()
+ .orElse(null);
+
+ // Convert property groups from the schema, merging in current values
+ final List<PropertyGroupConfigurationDTO> propertyGroupDtos = step.getPropertyGroups().stream()
+ .map(propertyGroup -> createPropertyGroupConfigurationDtoFromGroup(propertyGroup, stepConfig))
+ .collect(Collectors.toList());
+ dto.setPropertyGroupConfigurations(propertyGroupDtos);
+ return dto;
+ }
+
+ private PropertyGroupConfigurationDTO createPropertyGroupConfigurationDtoFromGroup(final ConnectorPropertyGroup propertyGroup, final ConfigurationStepConfiguration stepConfig) {
+ if (propertyGroup == null) {
+ return null;
+ }
+
+ final PropertyGroupConfigurationDTO dto = new PropertyGroupConfigurationDTO();
+ dto.setPropertyGroupName(propertyGroup.getName());
+ dto.setPropertyGroupDescription(propertyGroup.getDescription());
+
+ // Convert property descriptors from the schema, keyed by property name
+ // Use LinkedHashMap to preserve the order from the connector
+ final Map<String, ConnectorPropertyDescriptorDTO> propertyDescriptorMap = new LinkedHashMap<>();
+ for (final ConnectorPropertyDescriptor propertyDescriptor : propertyGroup.getProperties()) {
+ final ConnectorPropertyDescriptorDTO descriptorDto = createConnectorPropertyDescriptorDto(propertyDescriptor);
+ propertyDescriptorMap.put(descriptorDto.getName(), descriptorDto);
+ }
+ dto.setPropertyDescriptors(propertyDescriptorMap);
+
+ // Get the current property values for this group if they exist
+ // Use LinkedHashMap to preserve the order from the connector
+ final Map<String, ConnectorValueReferenceDTO> propertyValues = new LinkedHashMap<>();
+ if (stepConfig != null) {
+ final PropertyGroupConfiguration groupConfig = stepConfig.propertyGroupConfigurations().stream()
+ .filter(g -> propertyGroup.getName().equals(g.groupName()))
+ .findFirst()
+ .orElse(null);
+
+ if (groupConfig != null) {
+ for (final Map.Entry<String, ConnectorValueReference> entry : groupConfig.propertyValues().entrySet()) {
+ propertyValues.put(entry.getKey(), createConnectorValueReferenceDto(entry.getValue()));
+ }
+ }
+ }
+
+ dto.setPropertyValues(propertyValues);
+ return dto;
+ }
+
+ private ConnectorValueReferenceDTO createConnectorValueReferenceDto(final ConnectorValueReference valueReference) {
+ if (valueReference == null) {
+ return null;
+ }
+
+ final ConnectorValueReferenceDTO dto = new ConnectorValueReferenceDTO();
+ dto.setValueType(valueReference.getValueType() != null ? valueReference.getValueType().name() : null);
+
+ switch (valueReference) {
+ case StringLiteralValue stringLiteral -> dto.setValue(stringLiteral.getValue());
+ case AssetReference assetRef -> dto.setAssetIdentifier(assetRef.getAssetIdentifier());
+ case SecretReference secretRef -> {
+ dto.setSecretProviderId(secretRef.getProviderId());
+ dto.setSecretProviderName(secretRef.getProviderName());
+ dto.setSecretName(secretRef.getSecretName());
+ }
+ }
+
+ return dto;
+ }
+
+ private ConnectorPropertyDescriptorDTO createConnectorPropertyDescriptorDto(final ConnectorPropertyDescriptor propertyDescriptor) {
+ if (propertyDescriptor == null) {
+ return null;
+ }
+
+ final ConnectorPropertyDescriptorDTO dto = new ConnectorPropertyDescriptorDTO();
+ dto.setName(propertyDescriptor.getName());
+ dto.setDescription(propertyDescriptor.getDescription());
+ dto.setDefaultValue(propertyDescriptor.getDefaultValue());
+ dto.setRequired(propertyDescriptor.isRequired());
+ dto.setType(propertyDescriptor.getType() != null ? propertyDescriptor.getType().name() : null);
+ dto.setAllowableValuesFetchable(propertyDescriptor.isAllowableValuesFetchable());
+
+ // Convert allowable values if present
+ if (propertyDescriptor.getAllowableValues() != null && !propertyDescriptor.getAllowableValues().isEmpty()) {
+ final List<AllowableValueEntity> allowableValueEntities = propertyDescriptor.getAllowableValues().stream()
+ .map(describedValue -> {
+ final AllowableValueDTO allowableValueDto = new AllowableValueDTO();
+ allowableValueDto.setValue(describedValue.getValue());
+ allowableValueDto.setDisplayName(describedValue.getDisplayName());
+ allowableValueDto.setDescription(describedValue.getDescription());
+
+ final AllowableValueEntity entity = new AllowableValueEntity();
+ entity.setAllowableValue(allowableValueDto);
+ entity.setCanRead(true);
+ return entity;
+ })
+ .collect(Collectors.toList());
+ dto.setAllowableValues(allowableValueEntities);
+ }
+
+ // Convert dependencies if present
+ if (propertyDescriptor.getDependencies() != null && !propertyDescriptor.getDependencies().isEmpty()) {
+ final Set<ConnectorPropertyDependencyDTO> dependencyDtos = propertyDescriptor.getDependencies().stream()
+ .map(dependency -> {
+ final ConnectorPropertyDependencyDTO dependencyDto = new ConnectorPropertyDependencyDTO();
+ dependencyDto.setPropertyName(dependency.getPropertyName());
+ dependencyDto.setDependentValues(dependency.getDependentValues());
+ return dependencyDto;
+ })
+ .collect(Collectors.toSet());
+ dto.setDependencies(dependencyDtos);
+ }
+
+ return dto;
+ }
+
+ /**
+ * Creates an AllowableValueDTO from the specified AllowableValue.
+ *
+ * @param allowableValue the allowable value
+ * @return the DTO
+ */
+ public AllowableValueDTO createAllowableValueDto(final AllowableValue allowableValue) {
+ if (allowableValue == null) {
+ return null;
+ }
+
+ final AllowableValueDTO dto = new AllowableValueDTO();
+ dto.setValue(allowableValue.getValue());
+ dto.setDisplayName(allowableValue.getDisplayName());
+ dto.setDescription(allowableValue.getDescription());
+ return dto;
+ }
+
+ /**
+ * Creates a ConfigVerificationResultDTO from the specified ConfigVerificationResult.
+ *
+ * @param result the verification result
+ * @return the DTO
+ */
+ public ConfigVerificationResultDTO createConfigVerificationResultDto(final ConfigVerificationResult result) {
+ final ConfigVerificationResultDTO dto = new ConfigVerificationResultDTO();
+ dto.setExplanation(result.getExplanation());
+ dto.setOutcome(result.getOutcome().name());
+ dto.setVerificationStepName(result.getVerificationStepName());
+ dto.setSubject(result.getSubject());
+ return dto;
+ }
+
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
index f17f278144..ab4f27e2f8 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
@@ -20,6 +20,10 @@ import org.apache.nifi.web.api.dto.action.ActionDTO;
import org.apache.nifi.web.api.dto.diagnostics.ProcessorDiagnosticsDTO;
import org.apache.nifi.web.api.dto.flow.FlowBreadcrumbDTO;
import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
+import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
+import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
import org.apache.nifi.web.api.dto.status.ConnectionStatisticsDTO;
import org.apache.nifi.web.api.dto.status.ConnectionStatisticsSnapshotDTO;
import org.apache.nifi.web.api.dto.status.ConnectionStatusDTO;
@@ -124,6 +128,21 @@ public final class EntityFactory {
return entity;
}
+ public ConnectorEntity createConnectorEntity(final ConnectorDTO dto,
+ final RevisionDTO revision,
+ final PermissionsDTO permissions) {
+ final ConnectorEntity entity = new ConnectorEntity();
+ entity.setRevision(revision);
+ if (dto != null) {
+ entity.setPermissions(permissions);
+ entity.setId(dto.getId());
+ if (permissions != null && permissions.getCanRead()) {
+ entity.setComponent(dto);
+ }
+ }
+ return entity;
+ }
+
public ProcessorStatusSnapshotEntity createProcessorStatusSnapshotEntity(final ProcessorStatusSnapshotDTO status, final PermissionsDTO permissions) {
final ProcessorStatusSnapshotEntity entity = new ProcessorStatusSnapshotEntity();
entity.setId(status.getId());
@@ -842,4 +861,63 @@ public final class EntityFactory {
public NarSummaryEntity createNarSummaryEntity(final NarSummaryDTO narSummaryDTO) {
return new NarSummaryEntity(narSummaryDTO);
}
+
+ public ConfigurationStepNamesEntity createConfigurationStepNamesEntity(final ConnectorDTO connectorDto) {
+ final ConfigurationStepNamesEntity entity = new ConfigurationStepNamesEntity();
+
+ final List<String> configurationStepNames;
+ if (connectorDto != null
+ && connectorDto.getWorkingConfiguration() != null
+ && connectorDto.getWorkingConfiguration().getConfigurationStepConfigurations() != null) {
+
+ configurationStepNames = connectorDto.getWorkingConfiguration().getConfigurationStepConfigurations()
+ .stream()
+ .map(ConfigurationStepConfigurationDTO::getConfigurationStepName)
+ .collect(Collectors.toList());
+ } else {
+ configurationStepNames = List.of();
+ }
+
+ entity.setConfigurationStepNames(configurationStepNames);
+ return entity;
+ }
+
+ public ConfigurationStepEntity createConfigurationStepEntity(final ConnectorDTO connectorDto,
+ final String configurationStepName, final RevisionDTO parentConnectorRevision) {
+ ConfigurationStepConfigurationDTO foundConfigurationStep = null;
+
+ if (connectorDto != null
+ && connectorDto.getWorkingConfiguration() != null
+ && connectorDto.getWorkingConfiguration().getConfigurationStepConfigurations() != null) {
+
+ foundConfigurationStep = connectorDto.getWorkingConfiguration().getConfigurationStepConfigurations()
+ .stream()
+ .filter(step -> configurationStepName.equals(step.getConfigurationStepName()))
+ .findFirst()
+ .orElse(null);
+ }
+
+ if (foundConfigurationStep == null) {
+ final String connectorId = connectorDto != null ? connectorDto.getId() : "null";
+ throw new IllegalArgumentException(
+ String.format("Configuration step '%s' not found for connector '%s'.", configurationStepName, connectorId));
+ }
+
+ final ConfigurationStepEntity entity = new ConfigurationStepEntity();
+ entity.setConfigurationStep(foundConfigurationStep);
+ entity.setParentConnectorId(connectorDto != null ? connectorDto.getId() : null);
+ entity.setParentConnectorRevision(parentConnectorRevision);
+ return entity;
+ }
+
+ public ConnectorPropertyAllowableValuesEntity createConnectorPropertyAllowableValuesEntity(
+ final String configurationStepName, final String propertyGroupName, final String propertyName,
+ final List<AllowableValueEntity> allowableValues) {
+ final ConnectorPropertyAllowableValuesEntity entity = new ConnectorPropertyAllowableValuesEntity();
+ entity.setConfigurationStepName(configurationStepName);
+ entity.setPropertyGroupName(propertyGroupName);
+ entity.setPropertyName(propertyName);
+ entity.setAllowableValues(allowableValues);
+ return entity;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
index 02e23dbe21..24b4a42c6c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/configuration/WebApplicationConfiguration.java
@@ -33,7 +33,9 @@ import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.ContentAccess;
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.NiFiServiceFacadeLock;
+import org.apache.nifi.web.NiFiConnectorWebContext;
import org.apache.nifi.web.NiFiWebConfigurationContext;
+import org.apache.nifi.web.StandardNiFiConnectorWebContext;
import org.apache.nifi.web.StandardNiFiContentAccess;
import org.apache.nifi.web.StandardNiFiServiceFacade;
import org.apache.nifi.web.StandardNiFiWebConfigurationContext;
@@ -47,6 +49,7 @@ import org.apache.nifi.web.api.metrics.jmx.StandardJmxMetricsService;
import org.apache.nifi.web.controller.ControllerFacade;
import org.apache.nifi.web.controller.ControllerSearchService;
import org.apache.nifi.web.dao.AccessPolicyDAO;
+import org.apache.nifi.web.dao.ConnectorDAO;
import org.apache.nifi.web.dao.impl.ComponentDAO;
import org.apache.nifi.web.revision.RevisionManager;
import org.apache.nifi.web.search.query.RegexSearchQueryParser;
@@ -104,6 +107,8 @@ public class WebApplicationConfiguration {
private NiFiServiceFacade serviceFacade;
+ private ConnectorDAO connectorDAO;
+
public WebApplicationConfiguration(
final Authorizer authorizer,
final AccessPolicyDAO accessPolicyDao,
@@ -145,6 +150,11 @@ public class WebApplicationConfiguration {
this.serviceFacade = serviceFacade;
}
+ @Autowired
+ public void setConnectorDAO(final ConnectorDAO connectorDAO) {
+ this.connectorDAO = connectorDAO;
+ }
+
@Bean
public EntityFactory entityFactory() {
return new EntityFactory();
@@ -254,6 +264,13 @@ public class WebApplicationConfiguration {
return context;
}
+ @Bean
+ public NiFiConnectorWebContext nifiConnectorWebContext() {
+ final StandardNiFiConnectorWebContext context = new StandardNiFiConnectorWebContext();
+ context.setConnectorDAO(connectorDAO);
+ return context;
+ }
+
@Bean
public ControllerFacade controllerFacade() {
final ControllerFacade controllerFacade = new ControllerFacade();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index c2fbae3f27..7bc8615c8b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -44,6 +44,8 @@ import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.RequiredPermission;
import org.apache.nifi.components.listen.ListenComponent;
+import org.apache.nifi.components.connector.Connector;
+import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.Connection;
import org.apache.nifi.connectable.Port;
@@ -493,6 +495,18 @@ public class ControllerFacade implements Authorizable {
return dtoFactory.fromDocumentedTypes(getExtensionManager().getExtensions(Processor.class), bundleGroupFilter, bundleArtifactFilter, typeFilter);
}
+ /**
+ * Gets the Connector types that this controller supports.
+ *
+ * @param bundleGroupFilter if specified, must be member of bundle group
+ * @param bundleArtifactFilter if specified, must be member of bundle artifact
+ * @param typeFilter if specified, type must match
+ * @return types
+ */
+ public Set<DocumentedTypeDTO> getConnectorTypes(final String bundleGroupFilter, final String bundleArtifactFilter, final String typeFilter) {
+ return dtoFactory.fromDocumentedTypes(getExtensionManager().getExtensions(Connector.class), bundleGroupFilter, bundleArtifactFilter, typeFilter);
+ }
+
/**
* Gets the FlowFileComparator types that this controller supports.
*
@@ -1013,6 +1027,7 @@ public class ControllerFacade implements Authorizable {
resources.add(ResourceFactory.getResourceResource());
resources.add(ResourceFactory.getSiteToSiteResource());
resources.add(ResourceFactory.getParameterContextsResource());
+ resources.add(ResourceFactory.getConnectorsResource());
// add each parameter context
flowController.getFlowManager().getParameterContextManager().getParameterContexts().forEach(parameterContext -> resources.add(parameterContext.getResource()));
@@ -1129,6 +1144,14 @@ public class ControllerFacade implements Authorizable {
resources.add(ResourceFactory.getOperationResource(flowRegistryResource));
}
+ // add each connector
+ for (final ConnectorNode connector : flowController.getFlowManager().getAllConnectors()) {
+ final Resource connectorResource = connector.getResource();
+ resources.add(connectorResource);
+ resources.add(ResourceFactory.getPolicyResource(connectorResource));
+ resources.add(ResourceFactory.getOperationResource(connectorResource));
+ }
+
return resources;
}
@@ -1914,6 +1937,25 @@ public class ControllerFacade implements Authorizable {
return listenPorts;
}
+ /**
+ * Searches within a connector's encapsulated process group for the specified term.
+ *
+ * @param searchLiteral search string specified by the user
+ * @param connectorProcessGroup the connector's managed process group to search within
+ * @return result
+ */
+ public SearchResultsDTO searchConnector(final String searchLiteral, final ProcessGroup connectorProcessGroup) {
+ final SearchResultsDTO results = new SearchResultsDTO();
+ final SearchQuery searchQuery = searchQueryParser.parse(searchLiteral, NiFiUserUtils.getNiFiUser(), connectorProcessGroup, connectorProcessGroup);
+
+ if (!StringUtils.isEmpty(searchQuery.getTerm())) {
+ controllerSearchService.search(searchQuery, results);
+ controllerSearchService.searchParameters(searchQuery, results);
+ }
+
+ return results;
+ }
+
public void verifyComponentTypes(VersionedProcessGroup versionedFlow) {
flowController.verifyComponentTypesInSnippet(versionedFlow);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
new file mode 100644
index 0000000000..3ae78f4966
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
@@ -0,0 +1,59 @@
+/*
+ * 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.web.dao;
+
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+
+import java.util.List;
+
+public interface ConnectorDAO {
+
+ boolean hasConnector(String id);
+
+ ConnectorNode getConnector(String id);
+
+ List<ConnectorNode> getConnectors();
+
+ ConnectorNode createConnector(String type, String id, BundleCoordinate bundleCoordinate, boolean firstTimeAdded, boolean registerLogObserver);
+
+ void deleteConnector(String id);
+
+ void startConnector(String id);
+
+ void stopConnector(String id);
+
+ void enableConnector(String id);
+
+ void disableConnector(String id);
+
+ void updateConnectorConfigurationStep(String id, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
+
+ void applyConnectorUpdate(String id);
+
+ void verifyCanVerifyConfigurationStep(String id, String configurationStepName);
+
+ List<ConfigVerificationResult> verifyConfigurationStep(String id, String configurationStepName, List<PropertyGroupConfigurationDTO> propertyGroupConfigurations);
+
+ List<AllowableValue> fetchAllowableValues(String id, String stepName, String groupName, String propertyName, String filter);
+}
+
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
new file mode 100644
index 0000000000..fd07e81eb5
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
@@ -0,0 +1,203 @@
+/*
+ * 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.web.dao.impl;
+
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.connector.AssetReference;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.ConnectorValueReference;
+import org.apache.nifi.components.connector.ConnectorValueType;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.StringLiteralValue;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.flow.FlowManager;
+import org.apache.nifi.web.NiFiCoreException;
+import org.apache.nifi.web.ResourceNotFoundException;
+import org.apache.nifi.web.dao.ConnectorDAO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Repository
+public class StandardConnectorDAO implements ConnectorDAO {
+
+ private FlowController flowController;
+
+ @Autowired
+ public void setFlowController(final FlowController flowController) {
+ this.flowController = flowController;
+ }
+
+ private FlowManager getFlowManager() {
+ return flowController.getFlowManager();
+ }
+
+ private ConnectorRepository getConnectorRepository() {
+ return flowController.getConnectorRepository();
+ }
+
+ @Override
+ public boolean hasConnector(final String id) {
+ return getConnectorRepository().getConnector(id) != null;
+ }
+
+ @Override
+ public ConnectorNode getConnector(final String id) {
+ final ConnectorNode connector = getConnectorRepository().getConnector(id);
+ if (connector == null) {
+ throw new ResourceNotFoundException("Could not find Connector with ID " + id);
+ }
+ return connector;
+ }
+
+ @Override
+ public List<ConnectorNode> getConnectors() {
+ return getConnectorRepository().getConnectors();
+ }
+
+ @Override
+ public ConnectorNode createConnector(final String type, final String id, final BundleCoordinate bundleCoordinate, final boolean firstTimeAdded, final boolean registerLogObserver) {
+ final FlowManager flowManager = getFlowManager();
+ final ConnectorNode connector = flowManager.createConnector(type, id, bundleCoordinate, firstTimeAdded, registerLogObserver);
+ getConnectorRepository().addConnector(connector);
+ return connector;
+ }
+
+ @Override
+ public void deleteConnector(final String id) {
+ getConnectorRepository().removeConnector(id);
+ }
+
+ @Override
+ public void startConnector(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ getConnectorRepository().startConnector(connector);
+ }
+
+ @Override
+ public void stopConnector(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ getConnectorRepository().stopConnector(connector);
+ }
+
+ @Override
+ public void enableConnector(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ connector.enable();
+ }
+
+ @Override
+ public void disableConnector(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ connector.disable();
+ }
+
+ @Override
+ public void updateConnectorConfigurationStep(final String id, final String configurationStepName, final ConfigurationStepConfigurationDTO configurationStepDto) {
+ final ConnectorNode connector = getConnector(id);
+
+ // Convert DTO to domain object - extract the property groups from the configuration step
+ final List<PropertyGroupConfiguration> propertyGroups = new ArrayList<>();
+ if (configurationStepDto.getPropertyGroupConfigurations() != null) {
+ propertyGroups.addAll(configurationStepDto.getPropertyGroupConfigurations().stream()
+ .map(this::convertToPropertyGroupConfiguration)
+ .toList());
+ }
+
+ // Update the connector configuration through the repository
+ try {
+ getConnectorRepository().configureConnector(connector, configurationStepName, propertyGroups);
+ } catch (final Exception e) {
+ throw new IllegalStateException("Failed to update connector configuration: " + e, e);
+ }
+ }
+
+ private PropertyGroupConfiguration convertToPropertyGroupConfiguration(final PropertyGroupConfigurationDTO dto) {
+ final Map<String, ConnectorValueReference> propertyValues = new HashMap<>();
+ if (dto.getPropertyValues() != null) {
+ for (final Map.Entry<String, ConnectorValueReferenceDTO> entry : dto.getPropertyValues().entrySet()) {
+ propertyValues.put(entry.getKey(), convertToConnectorValueReference(entry.getValue()));
+ }
+ }
+ return new PropertyGroupConfiguration(dto.getPropertyGroupName(), propertyValues);
+ }
+
+ private ConnectorValueReference convertToConnectorValueReference(final ConnectorValueReferenceDTO dto) {
+ if (dto == null) {
+ return null;
+ }
+ final ConnectorValueType valueType = dto.getValueType() != null ? ConnectorValueType.valueOf(dto.getValueType()) : ConnectorValueType.STRING_LITERAL;
+ return switch (valueType) {
+ case STRING_LITERAL -> new StringLiteralValue(dto.getValue());
+ case ASSET_REFERENCE -> new AssetReference(dto.getAssetIdentifier());
+ case SECRET_REFERENCE -> new SecretReference(dto.getSecretProviderId(), dto.getSecretProviderName(), dto.getSecretName());
+ };
+ }
+
+ @Override
+ public void applyConnectorUpdate(final String id) {
+ final ConnectorNode connector = getConnector(id);
+ try {
+ getConnectorRepository().applyUpdate(connector);
+ } catch (final Exception e) {
+ throw new NiFiCoreException("Failed to apply connector update: " + e, e);
+ }
+ }
+
+ @Override
+ public void verifyCanVerifyConfigurationStep(final String id, final String configurationStepName) {
+ // Verify that the connector exists
+ getConnector(id);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String id, final String configurationStepName, final List<PropertyGroupConfigurationDTO> propertyGroupConfigurationDtos) {
+ final ConnectorNode connector = getConnector(id);
+
+ final List<PropertyGroupConfiguration> propertyGroupConfigurations = new ArrayList<>();
+ if (propertyGroupConfigurationDtos != null) {
+ for (final PropertyGroupConfigurationDTO dto : propertyGroupConfigurationDtos) {
+ propertyGroupConfigurations.add(convertToPropertyGroupConfiguration(dto));
+ }
+ }
+
+ return connector.verifyConfigurationStep(configurationStepName, propertyGroupConfigurations);
+ }
+
+ @Override
+ public List<AllowableValue> fetchAllowableValues(final String id, final String stepName, final String groupName, final String propertyName, final String filter) {
+ final ConnectorNode connector = getConnector(id);
+ if (filter == null || filter.isEmpty()) {
+ return connector.fetchAllowableValues(stepName, groupName, propertyName);
+ } else {
+ return connector.fetchAllowableValues(stepName, groupName, propertyName, filter);
+ }
+ }
+}
+
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
index 545e4d9e60..e5270f871b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
@@ -36,6 +36,8 @@ import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.FrameworkFlowContext;
import org.apache.nifi.controller.Counter;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.ProcessorNode;
@@ -94,6 +96,7 @@ import org.apache.nifi.web.api.dto.ProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.action.HistoryDTO;
import org.apache.nifi.web.api.dto.action.HistoryQueryDTO;
+import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.AffectedComponentEntity;
@@ -106,6 +109,7 @@ import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
import org.apache.nifi.web.controller.ControllerFacade;
+import org.apache.nifi.web.dao.ConnectorDAO;
import org.apache.nifi.web.dao.ProcessGroupDAO;
import org.apache.nifi.web.dao.RemoteProcessGroupDAO;
import org.apache.nifi.web.dao.UserDAO;
@@ -1550,6 +1554,38 @@ public class StandardNiFiServiceFacadeTest {
verify(dtoFactory, times(1)).createCountersDto(any());
}
+ @Test
+ public void testSearchConnector() {
+ final String connectorId = "connector-id";
+ final String searchQuery = "test-search";
+ final String managedGroupId = "managed-group-id";
+
+ final ConnectorDAO connectorDAO = mock(ConnectorDAO.class);
+ serviceFacade.setConnectorDAO(connectorDAO);
+
+ final ConnectorNode connectorNode = mock(ConnectorNode.class);
+ final FrameworkFlowContext flowContext = mock(FrameworkFlowContext.class);
+ final ProcessGroup managedProcessGroup = mock(ProcessGroup.class);
+
+ when(connectorDAO.getConnector(connectorId)).thenReturn(connectorNode);
+ when(connectorNode.getActiveFlowContext()).thenReturn(flowContext);
+ when(flowContext.getManagedProcessGroup()).thenReturn(managedProcessGroup);
+ when(managedProcessGroup.getIdentifier()).thenReturn(managedGroupId);
+
+ final ControllerFacade controllerFacade = mock(ControllerFacade.class);
+ final SearchResultsDTO expectedResults = new SearchResultsDTO();
+ when(controllerFacade.searchConnector(searchQuery, managedProcessGroup)).thenReturn(expectedResults);
+ serviceFacade.setControllerFacade(controllerFacade);
+
+ final SearchResultsDTO results = serviceFacade.searchConnector(connectorId, searchQuery);
+
+ assertNotNull(results);
+ verify(connectorDAO).getConnector(connectorId);
+ verify(connectorNode).getActiveFlowContext();
+ verify(flowContext).getManagedProcessGroup();
+ verify(controllerFacade).searchConnector(searchQuery, managedProcessGroup);
+ }
+
@Test
public void testUpdateAllCountersWithEmptyCounters() {
// Mock ControllerFacade to return empty list
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
new file mode 100644
index 0000000000..e1093d266d
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java
@@ -0,0 +1,387 @@
+/*
+ * 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.web.api;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriBuilder;
+import jakarta.ws.rs.core.UriInfo;
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.AuthorizeAccess;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.controller.ScheduledState;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.Revision;
+import org.apache.nifi.web.api.dto.AllowableValueDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.entity.AllowableValueEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
+import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity;
+import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity;
+import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.api.request.LongParameter;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.net.URI;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class TestConnectorResource {
+
+ @InjectMocks
+ private ConnectorResource connectorResource;
+
+ @Mock
+ private NiFiServiceFacade serviceFacade;
+
+ @Mock
+ private Authorizer authorizer;
+
+ @Mock
+ private HttpServletRequest httpServletRequest;
+
+ @Mock
+ private ServletContext servletContext;
+
+ @Mock
+ private NiFiProperties properties;
+
+ @Mock
+ private UriInfo uriInfo;
+
+ @Mock
+ private UriBuilder uriBuilder;
+
+ private static final String CONNECTOR_ID = "test-connector-id";
+ private static final String CONNECTOR_NAME = "Test Connector";
+ private static final String CONNECTOR_TYPE = "TestConnectorType";
+ private static final String CONFIGURATION_STEP_NAME = "test-step";
+ private static final String PROPERTY_GROUP_NAME = "test-group";
+ private static final String PROPERTY_NAME = "test-property";
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ lenient().when(httpServletRequest.getHeader(any())).thenReturn(null);
+ lenient().when(httpServletRequest.getServletContext()).thenReturn(servletContext);
+ lenient().when(httpServletRequest.getContextPath()).thenReturn("/nifi-api");
+ lenient().when(httpServletRequest.getScheme()).thenReturn("http");
+ lenient().when(httpServletRequest.getServerName()).thenReturn("localhost");
+ lenient().when(httpServletRequest.getServerPort()).thenReturn(8080);
+ lenient().when(servletContext.getInitParameter(any())).thenReturn(null);
+ lenient().when(properties.isNode()).thenReturn(Boolean.FALSE);
+
+ lenient().when(uriInfo.getBaseUriBuilder()).thenReturn(uriBuilder);
+ lenient().when(uriBuilder.segment(any(String[].class))).thenReturn(uriBuilder);
+ lenient().when(uriBuilder.build()).thenReturn(new URI("http://localhost:8080/nifi-api/connectors/" + CONNECTOR_ID));
+
+ connectorResource.setServiceFacade(serviceFacade);
+ connectorResource.httpServletRequest = httpServletRequest;
+ connectorResource.properties = properties;
+ connectorResource.uriInfo = uriInfo;
+ }
+
+ @Test
+ public void testGetConnector() {
+ final ConnectorEntity connectorEntity = createConnectorEntity();
+
+ when(serviceFacade.getConnector(CONNECTOR_ID)).thenReturn(connectorEntity);
+
+ try (Response response = connectorResource.getConnector(CONNECTOR_ID)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(connectorEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade).getConnector(CONNECTOR_ID);
+ }
+
+ @Test
+ public void testGetConnectorNotAuthorized() {
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () -> connectorResource.getConnector(CONNECTOR_ID));
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade, never()).getConnector(anyString());
+ }
+
+ @Test
+ public void testUpdateConnector() {
+ final ConnectorEntity requestEntity = createConnectorEntity();
+ final ConnectorEntity responseEntity = createConnectorEntity();
+
+ when(serviceFacade.updateConnector(any(Revision.class), any(ConnectorDTO.class))).thenReturn(responseEntity);
+
+ try (Response response = connectorResource.updateConnector(CONNECTOR_ID, requestEntity)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(responseEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).verifyUpdateConnector(any(ConnectorDTO.class));
+ verify(serviceFacade).updateConnector(any(Revision.class), any(ConnectorDTO.class));
+ }
+
+ @Test
+ public void testUpdateConnectorNotAuthorized() {
+ final ConnectorEntity requestEntity = createConnectorEntity();
+
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () -> connectorResource.updateConnector(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).verifyUpdateConnector(any(ConnectorDTO.class));
+ verify(serviceFacade, never()).updateConnector(any(Revision.class), any(ConnectorDTO.class));
+ }
+
+ @Test
+ public void testUpdateConnectorWithMismatchedId() {
+ final ConnectorEntity requestEntity = createConnectorEntity();
+ requestEntity.getComponent().setId("different-id");
+
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.updateConnector(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).updateConnector(any(Revision.class), any(ConnectorDTO.class));
+ }
+
+ @Test
+ public void testUpdateConnectorWithNullEntity() {
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.updateConnector(CONNECTOR_ID, null));
+
+ verify(serviceFacade, never()).updateConnector(any(Revision.class), any(ConnectorDTO.class));
+ }
+
+ @Test
+ public void testUpdateConnectorWithNullComponent() {
+ final ConnectorEntity requestEntity = new ConnectorEntity();
+ requestEntity.setComponent(null);
+
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.updateConnector(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).updateConnector(any(Revision.class), any(ConnectorDTO.class));
+ }
+
+ @Test
+ public void testUpdateConnectorWithNullRevision() {
+ final ConnectorEntity requestEntity = createConnectorEntity();
+ requestEntity.setRevision(null);
+
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.updateConnector(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).updateConnector(any(Revision.class), any(ConnectorDTO.class));
+ }
+
+ @Test
+ public void testDeleteConnector() {
+ final ConnectorEntity responseEntity = createConnectorEntity();
+
+ when(serviceFacade.deleteConnector(any(Revision.class), eq(CONNECTOR_ID))).thenReturn(responseEntity);
+
+ try (Response response = connectorResource.deleteConnector(new LongParameter("1"), new ClientIdParameter("client-id"), false, CONNECTOR_ID)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(responseEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).verifyDeleteConnector(CONNECTOR_ID);
+ verify(serviceFacade).deleteConnector(any(Revision.class), eq(CONNECTOR_ID));
+ }
+
+ @Test
+ public void testDeleteConnectorNotAuthorized() {
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () ->
+ connectorResource.deleteConnector(new LongParameter("1"), new ClientIdParameter("client-id"), false, CONNECTOR_ID));
+
+ verify(serviceFacade, never()).verifyDeleteConnector(anyString());
+ verify(serviceFacade, never()).deleteConnector(any(Revision.class), anyString());
+ }
+
+ @Test
+ public void testUpdateRunStatus() {
+ final ConnectorRunStatusEntity requestEntity = createConnectorRunStatusEntity();
+ final ConnectorEntity responseEntity = createConnectorEntity();
+
+ when(serviceFacade.scheduleConnector(any(Revision.class), eq(CONNECTOR_ID), eq(ScheduledState.RUNNING)))
+ .thenReturn(responseEntity);
+
+ try (Response response = connectorResource.updateRunStatus(CONNECTOR_ID, requestEntity)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(responseEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).verifyUpdateConnector(any(ConnectorDTO.class));
+ verify(serviceFacade).scheduleConnector(any(Revision.class), eq(CONNECTOR_ID), eq(ScheduledState.RUNNING));
+ }
+
+ @Test
+ public void testUpdateRunStatusNotAuthorized() {
+ final ConnectorRunStatusEntity requestEntity = createConnectorRunStatusEntity();
+
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () ->
+ connectorResource.updateRunStatus(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).verifyUpdateConnector(any(ConnectorDTO.class));
+ verify(serviceFacade, never()).scheduleConnector(any(Revision.class), anyString(), any(ScheduledState.class));
+ }
+
+ @Test
+ public void testUpdateRunStatusWithNullEntity() {
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.updateRunStatus(CONNECTOR_ID, null));
+
+ verify(serviceFacade, never()).scheduleConnector(any(Revision.class), anyString(), any(ScheduledState.class));
+ }
+
+ @Test
+ public void testUpdateRunStatusWithNullRevision() {
+ final ConnectorRunStatusEntity requestEntity = createConnectorRunStatusEntity();
+ requestEntity.setRevision(null);
+
+ assertThrows(IllegalArgumentException.class, () ->
+ connectorResource.updateRunStatus(CONNECTOR_ID, requestEntity));
+
+ verify(serviceFacade, never()).scheduleConnector(any(Revision.class), anyString(), any(ScheduledState.class));
+ }
+
+ @Test
+ public void testGetConnectorPropertyAllowableValues() {
+ final ConnectorPropertyAllowableValuesEntity responseEntity = createConnectorPropertyAllowableValuesEntity();
+
+ when(serviceFacade.getConnectorPropertyAllowableValues(CONNECTOR_ID, CONFIGURATION_STEP_NAME, PROPERTY_GROUP_NAME, PROPERTY_NAME, null))
+ .thenReturn(responseEntity);
+
+ try (Response response = connectorResource.getConnectorPropertyAllowableValues(CONNECTOR_ID, CONFIGURATION_STEP_NAME, PROPERTY_GROUP_NAME, PROPERTY_NAME, null)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(responseEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade).getConnectorPropertyAllowableValues(CONNECTOR_ID, CONFIGURATION_STEP_NAME, PROPERTY_GROUP_NAME, PROPERTY_NAME, null);
+ }
+
+ @Test
+ public void testGetConnectorPropertyAllowableValuesWithFilter() {
+ final String filter = "test-filter";
+ final ConnectorPropertyAllowableValuesEntity responseEntity = createConnectorPropertyAllowableValuesEntity();
+
+ when(serviceFacade.getConnectorPropertyAllowableValues(CONNECTOR_ID, CONFIGURATION_STEP_NAME, PROPERTY_GROUP_NAME, PROPERTY_NAME, filter))
+ .thenReturn(responseEntity);
+
+ try (Response response = connectorResource.getConnectorPropertyAllowableValues(CONNECTOR_ID, CONFIGURATION_STEP_NAME, PROPERTY_GROUP_NAME, PROPERTY_NAME, filter)) {
+ assertEquals(200, response.getStatus());
+ assertEquals(responseEntity, response.getEntity());
+ }
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade).getConnectorPropertyAllowableValues(CONNECTOR_ID, CONFIGURATION_STEP_NAME, PROPERTY_GROUP_NAME, PROPERTY_NAME, filter);
+ }
+
+ @Test
+ public void testGetConnectorPropertyAllowableValuesNotAuthorized() {
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () ->
+ connectorResource.getConnectorPropertyAllowableValues(CONNECTOR_ID, CONFIGURATION_STEP_NAME, PROPERTY_GROUP_NAME, PROPERTY_NAME, null));
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade, never()).getConnectorPropertyAllowableValues(anyString(), anyString(), anyString(), anyString(), any());
+ }
+
+ private ConnectorEntity createConnectorEntity() {
+ final ConnectorEntity entity = new ConnectorEntity();
+
+ final ConnectorDTO dto = new ConnectorDTO();
+ dto.setId(CONNECTOR_ID);
+ dto.setName(CONNECTOR_NAME);
+ dto.setType(CONNECTOR_TYPE);
+ dto.setState("STOPPED");
+ entity.setComponent(dto);
+
+ final RevisionDTO revision = new RevisionDTO();
+ revision.setVersion(1L);
+ revision.setClientId("client-id");
+ entity.setRevision(revision);
+
+ return entity;
+ }
+
+ private ConnectorRunStatusEntity createConnectorRunStatusEntity() {
+ final ConnectorRunStatusEntity entity = new ConnectorRunStatusEntity();
+ entity.setState("RUNNING");
+
+ final RevisionDTO revision = new RevisionDTO();
+ revision.setVersion(1L);
+ revision.setClientId("client-id");
+ entity.setRevision(revision);
+
+ return entity;
+ }
+
+ private ConnectorPropertyAllowableValuesEntity createConnectorPropertyAllowableValuesEntity() {
+ final ConnectorPropertyAllowableValuesEntity entity = new ConnectorPropertyAllowableValuesEntity();
+ entity.setConfigurationStepName(CONFIGURATION_STEP_NAME);
+ entity.setPropertyGroupName(PROPERTY_GROUP_NAME);
+ entity.setPropertyName(PROPERTY_NAME);
+
+ final AllowableValueDTO allowableValueDto1 = new AllowableValueDTO();
+ allowableValueDto1.setValue("value1");
+ allowableValueDto1.setDisplayName("Value 1");
+ allowableValueDto1.setDescription("First allowable value");
+
+ final AllowableValueEntity allowableValueEntity1 = new AllowableValueEntity();
+ allowableValueEntity1.setAllowableValue(allowableValueDto1);
+ allowableValueEntity1.setCanRead(true);
+
+ final AllowableValueDTO allowableValueDto2 = new AllowableValueDTO();
+ allowableValueDto2.setValue("value2");
+ allowableValueDto2.setDisplayName("Value 2");
+ allowableValueDto2.setDescription("Second allowable value");
+
+ final AllowableValueEntity allowableValueEntity2 = new AllowableValueEntity();
+ allowableValueEntity2.setAllowableValue(allowableValueDto2);
+ allowableValueEntity2.setCanRead(true);
+
+ entity.setAllowableValues(List.of(allowableValueEntity1, allowableValueEntity2));
+
+ return entity;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestFlowResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestFlowResource.java
index 00b32b89f7..ea0d3c4fa7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestFlowResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestFlowResource.java
@@ -29,6 +29,8 @@ import io.prometheus.client.exporter.common.TextFormat;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.StreamingOutput;
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.AuthorizeAccess;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.validation.DisabledServiceValidationResult;
import org.apache.nifi.connectable.Port;
@@ -54,10 +56,12 @@ import org.apache.nifi.web.api.dto.ComponentDifferenceDTO;
import org.apache.nifi.web.api.dto.DifferenceDTO;
import org.apache.nifi.web.api.entity.ActivateControllerServicesEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsForGroupRequestEntity;
+import org.apache.nifi.web.api.entity.ConnectorEntity;
import org.apache.nifi.web.api.entity.FlowComparisonEntity;
import org.apache.nifi.web.api.request.FlowMetricsProducer;
import org.apache.nifi.web.api.request.FlowMetricsReportingStrategy;
import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
@@ -94,6 +98,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -138,7 +143,17 @@ public class TestFlowResource {
private NiFiServiceFacade serviceFacade;
@Mock
- private NiFiProperties niFiProperties;
+ private NiFiProperties properties;
+
+ @Mock
+ private ConnectorResource connectorResource;
+
+ @BeforeEach
+ public void setUp() {
+ lenient().when(properties.isNode()).thenReturn(Boolean.FALSE);
+ resource.properties = properties;
+ resource.setConnectorResource(connectorResource);
+ }
@Test
public void testGetFlowMetricsProducerInvalid() {
@@ -450,7 +465,7 @@ public class TestFlowResource {
entity.setId(PROCESS_GROUP_ID);
entity.setState("ENABLED");
- when(niFiProperties.isNode()).thenReturn(false);
+ when(properties.isNode()).thenReturn(false);
resource.httpServletRequest = new MockHttpServletRequest();
final ArgumentCaptor<Function<ProcessGroup, Set<String>>> revisionsCaptor = ArgumentCaptor.captor();
@@ -601,6 +616,31 @@ public class TestFlowResource {
assertEquals(5, componentIds.size(), "Should have exactly 5 authorized components");
}
+ @Test
+ public void testGetConnectors() {
+ final ConnectorEntity connectorEntity = new ConnectorEntity();
+
+ when(serviceFacade.getConnectors()).thenReturn(Set.of(connectorEntity));
+
+ try (Response response = resource.getConnectors()) {
+ assertEquals(200, response.getStatus());
+ assertNotNull(response.getEntity());
+ }
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade).getConnectors();
+ }
+
+ @Test
+ public void testGetConnectorsNotAuthorized() {
+ doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+
+ assertThrows(AccessDeniedException.class, () -> resource.getConnectors());
+
+ verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class));
+ verify(serviceFacade, never()).getConnectors();
+ }
+
private void setUpGetVersionDifference() {
doReturn(getDifferences()).when(serviceFacade).getVersionDifference(anyString(), any(FlowVersionLocation.class), any(FlowVersionLocation.class));
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java
new file mode 100644
index 0000000000..9152558394
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/controller/ControllerFacadeTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.web.controller;
+
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
+import org.apache.nifi.web.search.query.SearchQuery;
+import org.apache.nifi.web.search.query.SearchQueryParser;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class ControllerFacadeTest {
+
+ private static final String SEARCH_TERM = "test-search";
+
+ @Mock
+ private SearchQueryParser searchQueryParser;
+
+ @Mock
+ private ControllerSearchService controllerSearchService;
+
+ @Mock
+ private ProcessGroup connectorProcessGroup;
+
+ @Mock
+ private SearchQuery searchQuery;
+
+ private ControllerFacade controllerFacade;
+
+ @BeforeEach
+ public void setUp() {
+ controllerFacade = new ControllerFacade();
+ controllerFacade.setSearchQueryParser(searchQueryParser);
+ controllerFacade.setControllerSearchService(controllerSearchService);
+
+ final NiFiUser user = new StandardNiFiUser.Builder().identity("test-user").build();
+ SecurityContextHolder.getContext().setAuthentication(new NiFiAuthenticationToken(new NiFiUserDetails(user)));
+ }
+
+ @Test
+ public void testSearchConnectorDelegatesSearchQueryParserWithConnectorProcessGroupAsRootAndActive() {
+ when(searchQueryParser.parse(eq(SEARCH_TERM), any(NiFiUser.class), eq(connectorProcessGroup), eq(connectorProcessGroup))).thenReturn(searchQuery);
+ when(searchQuery.getTerm()).thenReturn(SEARCH_TERM);
+
+ final SearchResultsDTO results = controllerFacade.searchConnector(SEARCH_TERM, connectorProcessGroup);
+
+ assertNotNull(results);
+ verify(searchQueryParser).parse(eq(SEARCH_TERM), any(NiFiUser.class), eq(connectorProcessGroup), eq(connectorProcessGroup));
+ verify(controllerSearchService).search(eq(searchQuery), any(SearchResultsDTO.class));
+ verify(controllerSearchService).searchParameters(eq(searchQuery), any(SearchResultsDTO.class));
+ }
+
+ @Test
+ public void testSearchConnectorWithEmptyTermDoesNotInvokeSearchService() {
+ when(searchQueryParser.parse(eq(""), any(NiFiUser.class), eq(connectorProcessGroup), eq(connectorProcessGroup))).thenReturn(searchQuery);
+ when(searchQuery.getTerm()).thenReturn("");
+
+ final SearchResultsDTO results = controllerFacade.searchConnector("", connectorProcessGroup);
+
+ assertNotNull(results);
+ verify(searchQueryParser).parse(eq(""), any(NiFiUser.class), eq(connectorProcessGroup), eq(connectorProcessGroup));
+ }
+
+ @Test
+ public void testSearchConnectorWithNullTermDoesNotInvokeSearchService() {
+ when(searchQueryParser.parse(eq(null), any(NiFiUser.class), eq(connectorProcessGroup), eq(connectorProcessGroup))).thenReturn(searchQuery);
+ when(searchQuery.getTerm()).thenReturn(null);
+
+ final SearchResultsDTO results = controllerFacade.searchConnector(null, connectorProcessGroup);
+
+ assertNotNull(results);
+ verify(searchQueryParser).parse(eq(null), any(NiFiUser.class), eq(connectorProcessGroup), eq(connectorProcessGroup));
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
new file mode 100644
index 0000000000..e33d2aded8
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.web.dao.impl;
+
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.web.NiFiCoreException;
+import org.apache.nifi.web.ResourceNotFoundException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class StandardConnectorDAOTest {
+
+ private StandardConnectorDAO connectorDAO;
+
+ @Mock
+ private FlowController flowController;
+
+ @Mock
+ private ConnectorRepository connectorRepository;
+
+ @Mock
+ private ConnectorNode connectorNode;
+
+ private static final String CONNECTOR_ID = "test-connector-id";
+ private static final String STEP_NAME = "test-step";
+ private static final String GROUP_NAME = "test-group";
+ private static final String PROPERTY_NAME = "test-property";
+
+ @BeforeEach
+ void setUp() {
+ connectorDAO = new StandardConnectorDAO();
+ connectorDAO.setFlowController(flowController);
+
+ when(flowController.getConnectorRepository()).thenReturn(connectorRepository);
+ }
+
+ @Test
+ void testApplyConnectorUpdate() throws Exception {
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID);
+
+ verify(connectorRepository).getConnector(CONNECTOR_ID);
+ verify(connectorRepository).applyUpdate(connectorNode);
+ }
+
+ @Test
+ void testApplyConnectorUpdateWithNonExistentConnector() throws Exception {
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(null);
+
+ final ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () ->
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID)
+ );
+
+ assertEquals("Could not find Connector with ID " + CONNECTOR_ID, exception.getMessage());
+ verify(connectorRepository).getConnector(CONNECTOR_ID);
+ verify(connectorRepository, never()).applyUpdate(any(ConnectorNode.class));
+ }
+
+ @Test
+ void testApplyConnectorUpdateWithFlowUpdateException() throws Exception {
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+ doThrow(new FlowUpdateException("Flow update failed")).when(connectorRepository).applyUpdate(connectorNode);
+
+ final NiFiCoreException exception = assertThrows(NiFiCoreException.class, () ->
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID)
+ );
+
+ assertEquals("Failed to apply connector update: org.apache.nifi.components.connector.FlowUpdateException: Flow update failed", exception.getMessage());
+ verify(connectorRepository).getConnector(CONNECTOR_ID);
+ verify(connectorRepository).applyUpdate(connectorNode);
+ }
+
+ @Test
+ void testApplyConnectorUpdateWithRuntimeException() throws Exception {
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+ doThrow(new RuntimeException("Test exception")).when(connectorRepository).applyUpdate(connectorNode);
+
+ final NiFiCoreException exception = assertThrows(NiFiCoreException.class, () ->
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID)
+ );
+
+ assertEquals("Failed to apply connector update: java.lang.RuntimeException: Test exception", exception.getMessage());
+ verify(connectorRepository).getConnector(CONNECTOR_ID);
+ verify(connectorRepository).applyUpdate(connectorNode);
+ }
+
+ @Test
+ void testApplyConnectorUpdateWithNullException() throws Exception {
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+ doThrow(new RuntimeException()).when(connectorRepository).applyUpdate(connectorNode);
+
+ final NiFiCoreException exception = assertThrows(NiFiCoreException.class, () ->
+ connectorDAO.applyConnectorUpdate(CONNECTOR_ID)
+ );
+
+ assertEquals("Failed to apply connector update: java.lang.RuntimeException", exception.getMessage());
+ verify(connectorRepository).getConnector(CONNECTOR_ID);
+ verify(connectorRepository).applyUpdate(connectorNode);
+ }
+
+ @Test
+ void testGetConnectorWithNonExistentId() {
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(null);
+
+ assertThrows(ResourceNotFoundException.class, () ->
+ connectorDAO.getConnector(CONNECTOR_ID)
+ );
+
+ verify(connectorRepository).getConnector(CONNECTOR_ID);
+ }
+
+ @Test
+ void testGetConnectorSuccess() {
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+
+ final ConnectorNode result = connectorDAO.getConnector(CONNECTOR_ID);
+
+ assertEquals(connectorNode, result);
+ verify(connectorRepository).getConnector(CONNECTOR_ID);
+ }
+
+ @Test
+ void testFetchAllowableValuesWithoutFilter() {
+ final List<AllowableValue> expectedValues = List.of(
+ new AllowableValue("value1", "Value 1", "First value"),
+ new AllowableValue("value2", "Value 2", "Second value")
+ );
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+ when(connectorNode.fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME)).thenReturn(expectedValues);
+
+ final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, GROUP_NAME, PROPERTY_NAME, null);
+
+ assertEquals(expectedValues, result);
+ verify(connectorNode).fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME);
+ verify(connectorNode, never()).fetchAllowableValues(any(), any(), any(), any());
+ }
+
+ @Test
+ void testFetchAllowableValuesWithEmptyFilter() {
+ final List<AllowableValue> expectedValues = List.of(
+ new AllowableValue("value1", "Value 1", "First value")
+ );
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+ when(connectorNode.fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME)).thenReturn(expectedValues);
+
+ final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, GROUP_NAME, PROPERTY_NAME, "");
+
+ assertEquals(expectedValues, result);
+ verify(connectorNode).fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME);
+ verify(connectorNode, never()).fetchAllowableValues(any(), any(), any(), any());
+ }
+
+ @Test
+ void testFetchAllowableValuesWithFilter() {
+ final String filter = "test-filter";
+ final List<AllowableValue> expectedValues = List.of(
+ new AllowableValue("filtered-value", "Filtered Value", "Filtered result")
+ );
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
+ when(connectorNode.fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME, filter)).thenReturn(expectedValues);
+
+ final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, GROUP_NAME, PROPERTY_NAME, filter);
+
+ assertEquals(expectedValues, result);
+ verify(connectorNode).fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME, filter);
+ verify(connectorNode, never()).fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME);
+ }
+
+ @Test
+ void testFetchAllowableValuesWithNonExistentConnector() {
+ when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(null);
+
+ assertThrows(ResourceNotFoundException.class, () ->
+ connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, GROUP_NAME, PROPERTY_NAME, null)
+ );
+
+ verify(connectorRepository).getConnector(CONNECTOR_ID);
+ }
+}
+
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
index 8486e1882d..742ba8dccc 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/component-access-policies/component-access-policies.component.ts
@@ -268,7 +268,8 @@ export class ComponentAccessPolicies implements OnInit, OnDestroy {
}
} else if (
policyComponentState.resource === 'parameter-contexts' ||
- policyComponentState.resource === 'parameter-providers'
+ policyComponentState.resource === 'parameter-providers' ||
+ policyComponentState.resource === 'connectors'
) {
switch (option.value) {
case 'read-data':
@@ -331,6 +332,7 @@ export class ComponentAccessPolicies implements OnInit, OnDestroy {
case 'parameter-providers':
case 'parameter-contexts':
case 'reporting-tasks':
+ case 'connectors':
return 'icon-drop';
}
@@ -357,6 +359,8 @@ export class ComponentAccessPolicies implements OnInit, OnDestroy {
return 'Reporting Task';
case 'parameter-providers':
return ComponentType.ParameterProvider;
+ case 'connectors':
+ return 'Connector';
}
return ComponentType.ProcessGroup;
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
index 74226feb68..d84692cf9d 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/access-policies/ui/global-access-policies/global-access-policies.component.ts
@@ -201,6 +201,7 @@ export class GlobalAccessPolicies implements OnInit, OnDestroy {
return (
resource === 'controller' ||
resource === 'parameter-contexts' ||
+ resource === 'connectors' ||
resource === 'counters' ||
resource === 'policies' ||
resource === 'tenants'
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.reducer.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.reducer.ts
index a1b57ea94f..3825549351 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.reducer.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.reducer.ts
@@ -34,6 +34,7 @@ export const initialState: CurrentUserState = {
controllerPermissions: NO_PERMISSIONS,
countersPermissions: NO_PERMISSIONS,
parameterContextPermissions: NO_PERMISSIONS,
+ connectorsPermissions: NO_PERMISSIONS,
policiesPermissions: NO_PERMISSIONS,
provenancePermissions: NO_PERMISSIONS,
restrictedComponentsPermissions: NO_PERMISSIONS,
diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/index.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/index.ts
index 4dfd972c66..2f05556c7c 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/index.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/index.ts
@@ -41,6 +41,7 @@ export interface CurrentUser {
policiesPermissions: Permissions;
systemPermissions: Permissions;
parameterContextPermissions: Permissions;
+ connectorsPermissions: Permissions;
restrictedComponentsPermissions: Permissions;
componentRestrictionPermissions: ComponentRestrictionPermission[];
}
diff --git a/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.ts b/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.ts
index bb6e285c85..75d5973b5c 100644
--- a/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.ts
+++ b/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.ts
@@ -63,6 +63,11 @@ export class NiFiCommon {
value: 'parameter-contexts',
description: 'Allows users to view/modify Parameter Contexts'
},
+ {
+ text: 'access connectors',
+ value: 'connectors',
+ description: 'Allows users to view/modify Connectors'
+ },
{
text: 'query provenance',
value: 'provenance',
(nifi) 08/40: NIFI-15326: Adding support of configuration step documentation. (#10631)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 21de437b8a0497944d79417993391c146f6c3c16
Author: Matt Gilman <ma...@gmail.com>
AuthorDate: Thu Dec 11 09:27:55 2025 -0500
NIFI-15326: Adding support of configuration step documentation. (#10631)
NIFI-15322: Adapting to new connector configuration step model.
---
.../connectors/kafkas3/KafkaConnectionStep.java | 32 ++++++++
.../api/dto/ConfigurationStepConfigurationDTO.java | 13 +++
.../org/apache/nifi/audit/ConnectorAuditor.java | 94 ++++++++--------------
.../org/apache/nifi/web/NiFiServiceFacade.java | 5 +-
.../apache/nifi/web/StandardNiFiServiceFacade.java | 8 +-
.../org/apache/nifi/web/api/ConnectorResource.java | 5 +-
.../org/apache/nifi/web/api/dto/DtoFactory.java | 29 ++++---
.../org/apache/nifi/web/api/dto/EntityFactory.java | 3 +-
.../java/org/apache/nifi/web/dao/ConnectorDAO.java | 5 +-
.../nifi/web/dao/impl/StandardConnectorDAO.java | 53 +++++-------
.../web/dao/impl/StandardConnectorDAOTest.java | 27 +++----
11 files changed, 137 insertions(+), 137 deletions(-)
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java
index 793b1e9be0..22ae253a98 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java
@@ -29,6 +29,37 @@ import java.util.List;
public class KafkaConnectionStep {
public static final String STEP_NAME = "Kafka Connection";
+ // Temporary documentation to verify the inline documentation feature in the UI.
+ // In practice, this content should probably be loaded from an external file.
+ private static final String KAFKA_CONNECTION_DOCUMENTATION = """
+ # Kafka Connection Configuration
+
+ This step configures the connection to your Apache Kafka cluster.
+
+ ## Kafka Server Settings
+
+ Enter the bootstrap servers for your Kafka cluster. You can specify multiple brokers
+ as a comma-separated list (e.g., `broker1:9092,broker2:9092,broker3:9092`).
+
+ ### Security Configuration
+
+ Select the appropriate security protocol based on your Kafka cluster configuration:
+
+ | Protocol | Description |
+ |----------|-------------|
+ | PLAINTEXT | No encryption or authentication |
+ | SSL | TLS encryption without SASL authentication |
+ | SASL_PLAINTEXT | SASL authentication without encryption |
+ | SASL_SSL | Both SASL authentication and TLS encryption (recommended) |
+
+ If using SASL authentication, provide your username and password credentials.
+
+ ## Schema Registry (Optional)
+
+ If your Kafka topics use Avro, Protobuf, or JSON Schema, configure the Schema Registry
+ URL to enable schema-based serialization and deserialization.
+ """;
+
public static final ConnectorPropertyDescriptor KAFKA_BROKERS = new ConnectorPropertyDescriptor.Builder()
.name("Kafka Brokers")
.description("A comma-separated list of Kafka brokers to connect to.")
@@ -127,6 +158,7 @@ public class KafkaConnectionStep {
public static final ConfigurationStep KAFKA_CONNECTION_STEP = new ConfigurationStep.Builder()
.name(STEP_NAME)
.description("Configure Kafka connection settings")
+ .documentation(KAFKA_CONNECTION_DOCUMENTATION)
.propertyGroups(List.of(
KAFKA_SERVER_GROUP,
SCHEMA_REGISTRY_GROUP
diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
index 9dd55b524c..94b0c7ff71 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigurationStepConfigurationDTO.java
@@ -29,6 +29,7 @@ public class ConfigurationStepConfigurationDTO {
private String configurationStepName;
private String configurationStepDescription;
+ private String configurationStepDocumentation;
private List<PropertyGroupConfigurationDTO> propertyGroupConfigurations;
/**
@@ -55,6 +56,18 @@ public class ConfigurationStepConfigurationDTO {
this.configurationStepDescription = configurationStepDescription;
}
+ /**
+ * @return the configuration step documentation in markdown
+ */
+ @Schema(description = "Extended documentation or help text for the configuration step.")
+ public String getConfigurationStepDocumentation() {
+ return configurationStepDocumentation;
+ }
+
+ public void setConfigurationStepDocumentation(final String configurationStepDocumentation) {
+ this.configurationStepDocumentation = configurationStepDocumentation;
+ }
+
/**
* @return the property group configurations
*/
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
index 18785143dc..e26e0d2aa3 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ConnectorAuditor.java
@@ -24,13 +24,13 @@ import org.apache.nifi.action.component.details.FlowChangeExtensionDetails;
import org.apache.nifi.action.details.ActionDetails;
import org.apache.nifi.action.details.FlowChangeConfigureDetails;
import org.apache.nifi.components.connector.AssetReference;
-import org.apache.nifi.components.connector.ConfigurationStepConfiguration;
import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorState;
import org.apache.nifi.components.connector.ConnectorValueReference;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.NamedStepConfiguration;
import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
@@ -224,17 +224,14 @@ public class ConnectorAuditor extends NiFiAuditor {
final ConfigurationStepConfigurationDTO configurationStepConfiguration, final ConnectorDAO connectorDAO) throws Throwable {
final ConnectorNode connector = connectorDAO.getConnector(connectorId);
- // Capture the current property values before the update
- final Map<String, Map<String, String>> previousValues = extractCurrentPropertyValues(connector, configurationStepName);
+ // Capture the current property values before the update (flat map: property name -> value)
+ final Map<String, String> previousValues = extractCurrentPropertyValues(connector, configurationStepName);
proceedingJoinPoint.proceed();
if (isAuditable()) {
- // Extract the new property values from the DTO
- final Map<String, Map<String, String>> newValues = extractPropertyValuesFromDto(configurationStepConfiguration);
-
- // Generate audit actions for each changed property
- final List<Action> actions = generateConfigurationChangeActions(connector, configurationStepName, previousValues, newValues);
+ // Generate audit actions for each changed property, using the DTO for group structure
+ final List<Action> actions = generateConfigurationChangeActions(connector, configurationStepName, previousValues, configurationStepConfiguration);
if (!actions.isEmpty()) {
saveActions(actions, logger);
}
@@ -246,23 +243,17 @@ public class ConnectorAuditor extends NiFiAuditor {
*
* @param connector the connector node
* @param configurationStepName the name of the configuration step
- * @return a map of property group name to property name to property value
+ * @return a map of property name to property value
*/
- private Map<String, Map<String, String>> extractCurrentPropertyValues(final ConnectorNode connector, final String configurationStepName) {
- final Map<String, Map<String, String>> result = new HashMap<>();
+ private Map<String, String> extractCurrentPropertyValues(final ConnectorNode connector, final String configurationStepName) {
+ final Map<String, String> result = new HashMap<>();
final ConnectorConfiguration configuration = connector.getWorkingFlowContext().getConfigurationContext().toConnectorConfiguration();
- for (final ConfigurationStepConfiguration stepConfig : configuration.getConfigurationStepConfigurations()) {
- if (Objects.equals(stepConfig.stepName(), configurationStepName)) {
- for (final PropertyGroupConfiguration groupConfig : stepConfig.propertyGroupConfigurations()) {
- final String groupName = groupConfig.groupName();
- final Map<String, String> propertyValues = new HashMap<>();
-
- for (final Map.Entry<String, ConnectorValueReference> entry : groupConfig.propertyValues().entrySet()) {
- propertyValues.put(entry.getKey(), formatValueReference(entry.getValue()));
- }
-
- result.put(groupName, propertyValues);
+ for (final NamedStepConfiguration namedStepConfig : configuration.getNamedStepConfigurations()) {
+ if (Objects.equals(namedStepConfig.stepName(), configurationStepName)) {
+ final StepConfiguration stepConfig = namedStepConfig.configuration();
+ for (final Map.Entry<String, ConnectorValueReference> entry : stepConfig.getPropertyValues().entrySet()) {
+ result.put(entry.getKey(), formatValueReference(entry.getValue()));
}
break;
}
@@ -271,33 +262,6 @@ public class ConnectorAuditor extends NiFiAuditor {
return result;
}
- /**
- * Extracts property values from the configuration step DTO.
- *
- * @param configurationStepDto the configuration step DTO
- * @return a map of property group name to property name to property value
- */
- private Map<String, Map<String, String>> extractPropertyValuesFromDto(final ConfigurationStepConfigurationDTO configurationStepDto) {
- final Map<String, Map<String, String>> result = new HashMap<>();
-
- if (configurationStepDto.getPropertyGroupConfigurations() != null) {
- for (final PropertyGroupConfigurationDTO groupDto : configurationStepDto.getPropertyGroupConfigurations()) {
- final String groupName = groupDto.getPropertyGroupName();
- final Map<String, String> propertyValues = new HashMap<>();
-
- if (groupDto.getPropertyValues() != null) {
- for (final Map.Entry<String, ConnectorValueReferenceDTO> entry : groupDto.getPropertyValues().entrySet()) {
- propertyValues.put(entry.getKey(), formatValueReferenceDto(entry.getValue()));
- }
- }
-
- result.put(groupName, propertyValues);
- }
- }
-
- return result;
- }
-
/**
* Formats a ConnectorValueReference as a string for audit logging.
*
@@ -340,33 +304,39 @@ public class ConnectorAuditor extends NiFiAuditor {
}
/**
- * Generates audit actions for configuration changes by comparing previous and new values.
+ * Generates audit actions for configuration changes by comparing previous values with the new values from the DTO.
*
* @param connector the connector node
* @param configurationStepName the configuration step name
- * @param previousValues the previous property values
- * @param newValues the new property values
+ * @param previousValues the previous property values (flat map: property name -> value)
+ * @param configurationStepDto the configuration step DTO containing new values with group structure
* @return list of actions for changed properties
*/
private List<Action> generateConfigurationChangeActions(final ConnectorNode connector, final String configurationStepName,
- final Map<String, Map<String, String>> previousValues, final Map<String, Map<String, String>> newValues) {
+ final Map<String, String> previousValues, final ConfigurationStepConfigurationDTO configurationStepDto) {
final List<Action> actions = new ArrayList<>();
final Date timestamp = new Date();
final FlowChangeExtensionDetails connectorDetails = new FlowChangeExtensionDetails();
connectorDetails.setType(connector.getComponentType());
- // Iterate through all property groups in the new values
- for (final Map.Entry<String, Map<String, String>> groupEntry : newValues.entrySet()) {
- final String groupName = groupEntry.getKey();
- final Map<String, String> newGroupValues = groupEntry.getValue();
- final Map<String, String> previousGroupValues = previousValues.getOrDefault(groupName, new HashMap<>());
+ if (configurationStepDto.getPropertyGroupConfigurations() == null) {
+ return actions;
+ }
+
+ // Iterate through all property groups in the DTO to preserve group names for audit logging
+ for (final PropertyGroupConfigurationDTO groupDto : configurationStepDto.getPropertyGroupConfigurations()) {
+ final String groupName = groupDto.getPropertyGroupName();
+
+ if (groupDto.getPropertyValues() == null) {
+ continue;
+ }
// Check each property in this group
- for (final Map.Entry<String, String> propertyEntry : newGroupValues.entrySet()) {
+ for (final Map.Entry<String, ConnectorValueReferenceDTO> propertyEntry : groupDto.getPropertyValues().entrySet()) {
final String propertyName = propertyEntry.getKey();
- final String newValue = propertyEntry.getValue();
- final String previousValue = previousGroupValues.get(propertyName);
+ final String newValue = formatValueReferenceDto(propertyEntry.getValue());
+ final String previousValue = previousValues.get(propertyName);
// Only create an action if the value changed
if (!Objects.equals(previousValue, newValue)) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 53232f8222..7fcef99ee2 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -82,9 +82,8 @@ import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
-import org.apache.nifi.web.api.dto.ConnectorDTO;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
-import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorDTO;
import org.apache.nifi.web.api.dto.ResourceDTO;
import org.apache.nifi.web.api.dto.SnippetDTO;
import org.apache.nifi.web.api.dto.SystemDiagnosticsDTO;
@@ -221,7 +220,7 @@ public interface NiFiServiceFacade {
void verifyCanVerifyConnectorConfigurationStep(String connectorId, String configurationStepName);
- List<ConfigVerificationResultDTO> performConnectorConfigurationStepVerification(String connectorId, String configurationStepName, List<PropertyGroupConfigurationDTO> propertyGroupConfigurations);
+ List<ConfigVerificationResultDTO> performConnectorConfigurationStepVerification(String connectorId, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
SearchResultsDTO searchConnector(String connectorId, String query);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 5cc7dd046f..740c66fa27 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -287,7 +287,6 @@ import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.ProcessorRunStatusDetailsDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
-import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.web.api.dto.PropertyHistoryDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
@@ -3697,8 +3696,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
@Override
public List<ConfigVerificationResultDTO> performConnectorConfigurationStepVerification(final String connectorId,
- final String configurationStepName, final List<PropertyGroupConfigurationDTO> propertyGroupConfigurations) {
- final List<ConfigVerificationResult> verificationResults = connectorDAO.verifyConfigurationStep(connectorId, configurationStepName, propertyGroupConfigurations);
+ final String configurationStepName, final ConfigurationStepConfigurationDTO configurationStepConfiguration) {
+ final List<ConfigVerificationResult> verificationResults = connectorDAO.verifyConfigurationStep(connectorId, configurationStepName, configurationStepConfiguration);
return verificationResults.stream()
.map(result -> dtoFactory.createConfigVerificationResultDto(result))
.collect(Collectors.toList());
@@ -3714,7 +3713,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
@Override
public ConnectorPropertyAllowableValuesEntity getConnectorPropertyAllowableValues(final String connectorId,
final String stepName, final String groupName, final String propertyName, final String filter) {
- final List<AllowableValue> allowableValues = connectorDAO.fetchAllowableValues(connectorId, stepName, groupName, propertyName, filter);
+ // groupName is retained for REST API backward compatibility but is no longer used by the underlying API
+ final List<AllowableValue> allowableValues = connectorDAO.fetchAllowableValues(connectorId, stepName, propertyName, filter);
final List<AllowableValueEntity> allowableValueEntities = allowableValues.stream()
.map(av -> entityFactory.createAllowableValueEntity(dtoFactory.createAllowableValueDto(av), true))
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
index 3c95ad6aa5..ffc3fc264d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java
@@ -56,7 +56,6 @@ import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.BundleDTO;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
import org.apache.nifi.web.api.dto.ConnectorDTO;
-import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
import org.apache.nifi.web.api.entity.ConfigurationStepEntity;
import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity;
@@ -1011,8 +1010,8 @@ public class ConnectorResource extends ApplicationResource {
final Consumer<AsynchronousWebRequest<VerifyConnectorConfigStepRequestEntity, List<ConfigVerificationResultDTO>>> updateTask = asyncRequest -> {
try {
- final List<PropertyGroupConfigurationDTO> propertyGroupConfigurations = requestDto.getConfigurationStep().getPropertyGroupConfigurations();
- final List<ConfigVerificationResultDTO> results = serviceFacade.performConnectorConfigurationStepVerification(connectorId, configurationStepName, propertyGroupConfigurations);
+ final ConfigurationStepConfigurationDTO configurationStep = requestDto.getConfigurationStep();
+ final List<ConfigVerificationResultDTO> results = serviceFacade.performConnectorConfigurationStepVerification(connectorId, configurationStepName, configurationStep);
asyncRequest.markStepComplete(results);
} catch (final Exception e) {
logger.error("Failed to verify Connector Configuration Step", e);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 9fd2885102..56067bdc5c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -73,15 +73,15 @@ import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.components.connector.AssetReference;
import org.apache.nifi.components.connector.ConfigurationStep;
-import org.apache.nifi.components.connector.ConfigurationStepConfiguration;
import org.apache.nifi.components.connector.ConnectorConfiguration;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
import org.apache.nifi.components.connector.ConnectorPropertyGroup;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.FrameworkFlowContext;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.NamedStepConfiguration;
import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.connectable.Connection;
@@ -5278,14 +5278,16 @@ public final class DtoFactory {
final ConfigurationStepConfigurationDTO dto = new ConfigurationStepConfigurationDTO();
dto.setConfigurationStepName(step.getName());
dto.setConfigurationStepDescription(step.getDescription());
+ dto.setConfigurationStepDocumentation(step.getDocumentation());
- // Get the current configuration values for this step
- final ConfigurationStepConfiguration stepConfig = configuration.getConfigurationStepConfigurations().stream()
+ // Get the current configuration values for this step from the flat StepConfiguration
+ final StepConfiguration stepConfig = configuration.getNamedStepConfigurations().stream()
.filter(c -> step.getName().equals(c.stepName()))
+ .map(NamedStepConfiguration::configuration)
.findFirst()
.orElse(null);
- // Convert property groups from the schema, merging in current values
+ // Convert property groups from the schema, merging in current values from flat stepConfig
final List<PropertyGroupConfigurationDTO> propertyGroupDtos = step.getPropertyGroups().stream()
.map(propertyGroup -> createPropertyGroupConfigurationDtoFromGroup(propertyGroup, stepConfig))
.collect(Collectors.toList());
@@ -5293,7 +5295,7 @@ public final class DtoFactory {
return dto;
}
- private PropertyGroupConfigurationDTO createPropertyGroupConfigurationDtoFromGroup(final ConnectorPropertyGroup propertyGroup, final ConfigurationStepConfiguration stepConfig) {
+ private PropertyGroupConfigurationDTO createPropertyGroupConfigurationDtoFromGroup(final ConnectorPropertyGroup propertyGroup, final StepConfiguration stepConfig) {
if (propertyGroup == null) {
return null;
}
@@ -5311,18 +5313,15 @@ public final class DtoFactory {
}
dto.setPropertyDescriptors(propertyDescriptorMap);
- // Get the current property values for this group if they exist
+ // Get the current property values for this group's properties from flat stepConfig
// Use LinkedHashMap to preserve the order from the connector
final Map<String, ConnectorValueReferenceDTO> propertyValues = new LinkedHashMap<>();
if (stepConfig != null) {
- final PropertyGroupConfiguration groupConfig = stepConfig.propertyGroupConfigurations().stream()
- .filter(g -> propertyGroup.getName().equals(g.groupName()))
- .findFirst()
- .orElse(null);
-
- if (groupConfig != null) {
- for (final Map.Entry<String, ConnectorValueReference> entry : groupConfig.propertyValues().entrySet()) {
- propertyValues.put(entry.getKey(), createConnectorValueReferenceDto(entry.getValue()));
+ // For each property in this group, check if there's a value in the flat stepConfig
+ for (final ConnectorPropertyDescriptor propertyDescriptor : propertyGroup.getProperties()) {
+ final ConnectorValueReference valueRef = stepConfig.getPropertyValue(propertyDescriptor.getName());
+ if (valueRef != null) {
+ propertyValues.put(propertyDescriptor.getName(), createConnectorValueReferenceDto(valueRef));
}
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
index 9ebe1fced4..144f7805fa 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
@@ -913,8 +913,7 @@ public final class EntityFactory {
}
public ConnectorPropertyAllowableValuesEntity createConnectorPropertyAllowableValuesEntity(
- final String configurationStepName, final String propertyGroupName, final String propertyName,
- final List<AllowableValueEntity> allowableValues) {
+ final String configurationStepName, final String propertyGroupName, final String propertyName, final List<AllowableValueEntity> allowableValues) {
final ConnectorPropertyAllowableValuesEntity entity = new ConnectorPropertyAllowableValuesEntity();
entity.setConfigurationStepName(configurationStepName);
entity.setPropertyGroupName(propertyGroupName);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
index 3ae78f4966..2a3ef15075 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ConnectorDAO.java
@@ -21,7 +21,6 @@ import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
-import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import java.util.List;
@@ -51,9 +50,9 @@ public interface ConnectorDAO {
void verifyCanVerifyConfigurationStep(String id, String configurationStepName);
- List<ConfigVerificationResult> verifyConfigurationStep(String id, String configurationStepName, List<PropertyGroupConfigurationDTO> propertyGroupConfigurations);
+ List<ConfigVerificationResult> verifyConfigurationStep(String id, String configurationStepName, ConfigurationStepConfigurationDTO configurationStepConfiguration);
- List<AllowableValue> fetchAllowableValues(String id, String stepName, String groupName, String propertyName, String filter);
+ List<AllowableValue> fetchAllowableValues(String id, String stepName, String propertyName, String filter);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
index fd07e81eb5..13de988b8b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectorDAO.java
@@ -24,21 +24,20 @@ import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.components.connector.ConnectorRepository;
import org.apache.nifi.components.connector.ConnectorValueReference;
import org.apache.nifi.components.connector.ConnectorValueType;
-import org.apache.nifi.components.connector.PropertyGroupConfiguration;
import org.apache.nifi.components.connector.SecretReference;
+import org.apache.nifi.components.connector.StepConfiguration;
import org.apache.nifi.components.connector.StringLiteralValue;
-import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
-import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
-import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.web.NiFiCoreException;
import org.apache.nifi.web.ResourceNotFoundException;
+import org.apache.nifi.web.api.dto.ConfigurationStepConfigurationDTO;
+import org.apache.nifi.web.api.dto.ConnectorValueReferenceDTO;
+import org.apache.nifi.web.api.dto.PropertyGroupConfigurationDTO;
import org.apache.nifi.web.dao.ConnectorDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -121,30 +120,29 @@ public class StandardConnectorDAO implements ConnectorDAO {
public void updateConnectorConfigurationStep(final String id, final String configurationStepName, final ConfigurationStepConfigurationDTO configurationStepDto) {
final ConnectorNode connector = getConnector(id);
- // Convert DTO to domain object - extract the property groups from the configuration step
- final List<PropertyGroupConfiguration> propertyGroups = new ArrayList<>();
- if (configurationStepDto.getPropertyGroupConfigurations() != null) {
- propertyGroups.addAll(configurationStepDto.getPropertyGroupConfigurations().stream()
- .map(this::convertToPropertyGroupConfiguration)
- .toList());
- }
+ // Convert DTO to domain object - flatten all property groups into a single StepConfiguration
+ final StepConfiguration stepConfiguration = convertToStepConfiguration(configurationStepDto);
// Update the connector configuration through the repository
try {
- getConnectorRepository().configureConnector(connector, configurationStepName, propertyGroups);
+ getConnectorRepository().configureConnector(connector, configurationStepName, stepConfiguration);
} catch (final Exception e) {
throw new IllegalStateException("Failed to update connector configuration: " + e, e);
}
}
- private PropertyGroupConfiguration convertToPropertyGroupConfiguration(final PropertyGroupConfigurationDTO dto) {
+ private StepConfiguration convertToStepConfiguration(final ConfigurationStepConfigurationDTO dto) {
final Map<String, ConnectorValueReference> propertyValues = new HashMap<>();
- if (dto.getPropertyValues() != null) {
- for (final Map.Entry<String, ConnectorValueReferenceDTO> entry : dto.getPropertyValues().entrySet()) {
- propertyValues.put(entry.getKey(), convertToConnectorValueReference(entry.getValue()));
+ if (dto.getPropertyGroupConfigurations() != null) {
+ for (final PropertyGroupConfigurationDTO groupDto : dto.getPropertyGroupConfigurations()) {
+ if (groupDto.getPropertyValues() != null) {
+ for (final Map.Entry<String, ConnectorValueReferenceDTO> entry : groupDto.getPropertyValues().entrySet()) {
+ propertyValues.put(entry.getKey(), convertToConnectorValueReference(entry.getValue()));
+ }
+ }
}
}
- return new PropertyGroupConfiguration(dto.getPropertyGroupName(), propertyValues);
+ return new StepConfiguration(propertyValues);
}
private ConnectorValueReference convertToConnectorValueReference(final ConnectorValueReferenceDTO dto) {
@@ -176,26 +174,19 @@ public class StandardConnectorDAO implements ConnectorDAO {
}
@Override
- public List<ConfigVerificationResult> verifyConfigurationStep(final String id, final String configurationStepName, final List<PropertyGroupConfigurationDTO> propertyGroupConfigurationDtos) {
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String id, final String configurationStepName, final ConfigurationStepConfigurationDTO configurationStepDto) {
final ConnectorNode connector = getConnector(id);
-
- final List<PropertyGroupConfiguration> propertyGroupConfigurations = new ArrayList<>();
- if (propertyGroupConfigurationDtos != null) {
- for (final PropertyGroupConfigurationDTO dto : propertyGroupConfigurationDtos) {
- propertyGroupConfigurations.add(convertToPropertyGroupConfiguration(dto));
- }
- }
-
- return connector.verifyConfigurationStep(configurationStepName, propertyGroupConfigurations);
+ final StepConfiguration stepConfiguration = convertToStepConfiguration(configurationStepDto);
+ return connector.verifyConfigurationStep(configurationStepName, stepConfiguration);
}
@Override
- public List<AllowableValue> fetchAllowableValues(final String id, final String stepName, final String groupName, final String propertyName, final String filter) {
+ public List<AllowableValue> fetchAllowableValues(final String id, final String stepName, final String propertyName, final String filter) {
final ConnectorNode connector = getConnector(id);
if (filter == null || filter.isEmpty()) {
- return connector.fetchAllowableValues(stepName, groupName, propertyName);
+ return connector.fetchAllowableValues(stepName, propertyName);
} else {
- return connector.fetchAllowableValues(stepName, groupName, propertyName, filter);
+ return connector.fetchAllowableValues(stepName, propertyName, filter);
}
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
index e33d2aded8..bb96b35271 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/dao/impl/StandardConnectorDAOTest.java
@@ -55,7 +55,6 @@ class StandardConnectorDAOTest {
private static final String CONNECTOR_ID = "test-connector-id";
private static final String STEP_NAME = "test-step";
- private static final String GROUP_NAME = "test-group";
private static final String PROPERTY_NAME = "test-property";
@BeforeEach
@@ -159,13 +158,13 @@ class StandardConnectorDAOTest {
new AllowableValue("value2", "Value 2", "Second value")
);
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
- when(connectorNode.fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME)).thenReturn(expectedValues);
+ when(connectorNode.fetchAllowableValues(STEP_NAME, PROPERTY_NAME)).thenReturn(expectedValues);
- final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, GROUP_NAME, PROPERTY_NAME, null);
+ final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, PROPERTY_NAME, null);
assertEquals(expectedValues, result);
- verify(connectorNode).fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME);
- verify(connectorNode, never()).fetchAllowableValues(any(), any(), any(), any());
+ verify(connectorNode).fetchAllowableValues(STEP_NAME, PROPERTY_NAME);
+ verify(connectorNode, never()).fetchAllowableValues(any(), any(), any());
}
@Test
@@ -174,13 +173,13 @@ class StandardConnectorDAOTest {
new AllowableValue("value1", "Value 1", "First value")
);
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
- when(connectorNode.fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME)).thenReturn(expectedValues);
+ when(connectorNode.fetchAllowableValues(STEP_NAME, PROPERTY_NAME)).thenReturn(expectedValues);
- final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, GROUP_NAME, PROPERTY_NAME, "");
+ final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, PROPERTY_NAME, "");
assertEquals(expectedValues, result);
- verify(connectorNode).fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME);
- verify(connectorNode, never()).fetchAllowableValues(any(), any(), any(), any());
+ verify(connectorNode).fetchAllowableValues(STEP_NAME, PROPERTY_NAME);
+ verify(connectorNode, never()).fetchAllowableValues(any(), any(), any());
}
@Test
@@ -190,13 +189,13 @@ class StandardConnectorDAOTest {
new AllowableValue("filtered-value", "Filtered Value", "Filtered result")
);
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(connectorNode);
- when(connectorNode.fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME, filter)).thenReturn(expectedValues);
+ when(connectorNode.fetchAllowableValues(STEP_NAME, PROPERTY_NAME, filter)).thenReturn(expectedValues);
- final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, GROUP_NAME, PROPERTY_NAME, filter);
+ final List<AllowableValue> result = connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, PROPERTY_NAME, filter);
assertEquals(expectedValues, result);
- verify(connectorNode).fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME, filter);
- verify(connectorNode, never()).fetchAllowableValues(STEP_NAME, GROUP_NAME, PROPERTY_NAME);
+ verify(connectorNode).fetchAllowableValues(STEP_NAME, PROPERTY_NAME, filter);
+ verify(connectorNode, never()).fetchAllowableValues(STEP_NAME, PROPERTY_NAME);
}
@Test
@@ -204,7 +203,7 @@ class StandardConnectorDAOTest {
when(connectorRepository.getConnector(CONNECTOR_ID)).thenReturn(null);
assertThrows(ResourceNotFoundException.class, () ->
- connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, GROUP_NAME, PROPERTY_NAME, null)
+ connectorDAO.fetchAllowableValues(CONNECTOR_ID, STEP_NAME, PROPERTY_NAME, null)
);
verify(connectorRepository).getConnector(CONNECTOR_ID);
(nifi) 17/40: NIFI-15369: Allow ConfigurationStep to depend on another (ConfigurationStep,Property) tuple (#10669)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 59b2ad0dcab9fb1cdc813bde764ef083fab630b2
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Fri Dec 19 14:24:07 2025 -0500
NIFI-15369: Allow ConfigurationStep to depend on another (ConfigurationStep,Property) tuple (#10669)
---
.../nifi/mock/connectors/GenerateAndLog.java | 2 +-
.../apache/nifi/connectors/kafkas3/KafkaToS3.java | 14 ++--
.../nifi/components/connector/ConnectorNode.java | 4 +-
.../nifi/components/connector/GhostConnector.java | 2 +-
.../connector/StandardConnectorNode.java | 4 +-
.../apache/nifi/controller/ExtensionBuilder.java | 2 +-
.../components/connector/BlockingConnector.java | 2 +-
.../connector/DynamicAllowableValuesConnector.java | 89 +++++++++++-----------
.../components/connector/DynamicFlowConnector.java | 2 +-
.../components/connector/ParameterConnector.java | 2 +-
.../components/connector/SleepingConnector.java | 2 +-
.../connector/StandardConnectorNodeIT.java | 40 +++++-----
.../connector/TestStandardConnectorNode.java | 4 +-
.../apache/nifi/controller/flow/NopConnector.java | 2 +-
.../java/org/apache/nifi/nar/DummyConnector.java | 2 +-
.../connectors/tests/system/AssetConnector.java | 2 +-
.../nifi/connectors/tests/system/NopConnector.java | 2 +-
17 files changed, 92 insertions(+), 85 deletions(-)
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
index f9d5aebffe..96b9fa63c3 100644
--- a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
@@ -40,7 +40,7 @@ public class GenerateAndLog extends AbstractConnector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of();
}
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java
index 43bf138845..5549695142 100644
--- a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java
@@ -52,16 +52,18 @@ import java.util.stream.Collectors;
@Tags({"kafka", "s3"})
public class KafkaToS3 extends AbstractConnector {
+ private static final List<ConfigurationStep> configurationSteps = List.of(
+ KafkaConnectionStep.KAFKA_CONNECTION_STEP,
+ KafkaTopicsStep.KAFKA_TOPICS_STEP,
+ S3Step.S3_STEP
+ );
+
private volatile CompletableFuture<Void> drainFlowFileFuture = null;
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
- return List.of(
- KafkaConnectionStep.KAFKA_CONNECTION_STEP,
- KafkaTopicsStep.KAFKA_TOPICS_STEP,
- S3Step.S3_STEP
- );
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return configurationSteps;
}
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
index b5c6d4820d..9dd1fe36a1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/components/connector/ConnectorNode.java
@@ -23,7 +23,6 @@ import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.VersionedComponent;
-import org.apache.nifi.components.connector.components.FlowContext;
import org.apache.nifi.components.validation.ValidationState;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.FlowFileTransferCounts;
@@ -195,8 +194,7 @@ public interface ConnectorNode extends ComponentAuthorizable, VersionedComponent
/**
* Updates the configuration of one of the configuration steps. This method should only be invoked via the ConnectorRepository.
* @param configurationStepName the name of the configuration step being set
- * (must match one of the names returned by {@link Connector#getConfigurationSteps(FlowContext)})
- * when providing the working flow context
+ * (must match one of the names returned by {@link Connector#getConfigurationSteps()})
* @param configuration the configuration for the given configuration step
* @throws FlowUpdateException if unable to apply the configuration changes
*/
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
index 5f37546e51..4c7c5fa45e 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/GhostConnector.java
@@ -79,7 +79,7 @@ public class GhostConnector implements Connector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext workingContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index a0dd520c70..59f2dfba10 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -814,7 +814,7 @@ public class StandardConnectorNode implements ConnectorNode {
@Override
public List<ConfigurationStep> getConfigurationSteps() {
try (NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, getConnector().getClass(), getIdentifier())) {
- return getConnector().getConfigurationSteps(workingFlowContext);
+ return getConnector().getConfigurationSteps();
}
}
@@ -939,7 +939,7 @@ public class StandardConnectorNode implements ConnectorNode {
private void validatePropertyReferences(final List<ValidationResult> allResults) {
- final List<ConfigurationStep> configurationSteps = getConnector().getConfigurationSteps(activeFlowContext);
+ final List<ConfigurationStep> configurationSteps = getConnector().getConfigurationSteps();
final ConnectorConfiguration connectorConfiguration = activeFlowContext.getConfigurationContext().toConnectorConfiguration();
for (final ConfigurationStep step : configurationSteps) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
index 5b29dcc34a..c1eac07a4c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/ExtensionBuilder.java
@@ -542,7 +542,7 @@ public class ExtensionBuilder {
private void initializeDefaultValues(final Connector connector, final FrameworkFlowContext flowContext) {
try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(extensionManager, connector.getClass(), identifier)) {
- final List<ConfigurationStep> configSteps = connector.getConfigurationSteps(flowContext);
+ final List<ConfigurationStep> configSteps = connector.getConfigurationSteps();
for (final ConfigurationStep step : configSteps) {
final Map<String, ConnectorValueReference> defaultValues = new HashMap<>();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
index a869970b16..2d163e5e80 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/BlockingConnector.java
@@ -78,7 +78,7 @@ public class BlockingConnector implements Connector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicAllowableValuesConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicAllowableValuesConnector.java
index 597ec52e7f..1f1a2ee869 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicAllowableValuesConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicAllowableValuesConnector.java
@@ -17,6 +17,7 @@
package org.apache.nifi.components.connector;
+import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
@@ -29,7 +30,6 @@ import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.flow.VersionedProcessor;
import java.io.File;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -52,55 +52,35 @@ public class DynamicAllowableValuesConnector extends AbstractConnector {
.propertyGroups(List.of(FILE_PROPERTY_GROUP))
.build();
+ static final ConnectorPropertyDescriptor FIRST_PRIMARY_COLOR = new ConnectorPropertyDescriptor.Builder()
+ .name("First Primary Color")
+ .description("The first primary color")
+ .allowableValuesFetchable(true)
+ .required(true)
+ .build();
- @Override
- public VersionedExternalFlow getInitialFlow() {
- return null;
- }
+ static final ConnectorPropertyGroup PRIMARY_COLORS_PROPERTY_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Primary Colors")
+ .addProperty(FIRST_PRIMARY_COLOR)
+ .build();
- @Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
- final List<ConfigurationStep> steps = new ArrayList<>();
- steps.add(FILE_STEP);
+ static final ConfigurationStep COLORS_STEP = new ConfigurationStep.Builder()
+ .name("Colors")
+ .propertyGroups(List.of(PRIMARY_COLORS_PROPERTY_GROUP))
+ .dependsOn(FILE_STEP, FILE_PATH)
+ .build();
- final ConnectorConfigurationContext configContext = flowContext.getConfigurationContext();
+ private static final List<ConfigurationStep> CONFIGURATION_STEPS = List.of(FILE_STEP, COLORS_STEP);
- if (configContext.getProperty(FILE_STEP, FILE_PATH) != null) {
- final Set<ProcessorFacade> processorsFacades = flowContext.getRootGroup().getProcessors();
- if (processorsFacades.isEmpty()) {
- return steps;
- }
- final ProcessorFacade processorFacade = processorsFacades.iterator().next();
- try {
- final List<String> fileValues = (List<String>) processorFacade.invokeConnectorMethod("getFileValues", Map.of());
- steps.add(createColorConfigurationStep(fileValues));
- } catch (final InvocationFailedException ignored) {
- // If we can't get file values, don't add the step.
- }
- }
-
- return steps;
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ return null;
}
- private ConfigurationStep createColorConfigurationStep(final List<String> values) {
- final ConnectorPropertyDescriptor FIRST_PRIMARY_COLOR = new ConnectorPropertyDescriptor.Builder()
- .name("First Primary Color")
- .description("The first primary color")
- .defaultValue(values.getFirst())
- .allowableValues(values.toArray(new String[0]))
- .required(true)
- .build();
-
- final ConnectorPropertyGroup PRIMARY_COLORS_PROPERTY_GROUP = new ConnectorPropertyGroup.Builder()
- .name("Primary Colors")
- .addProperty(FIRST_PRIMARY_COLOR)
- .build();
-
- return new ConfigurationStep.Builder()
- .name("Colors")
- .propertyGroups(List.of(PRIMARY_COLORS_PROPERTY_GROUP))
- .build();
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps() {
+ return CONFIGURATION_STEPS;
}
@Override
@@ -122,6 +102,29 @@ public class DynamicAllowableValuesConnector extends AbstractConnector {
return List.of();
}
+ @Override
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String propertyName, final FlowContext flowContext) {
+ if ("Colors".equals(stepName) && "First Primary Color".equals(propertyName)) {
+ final Set<ProcessorFacade> processorFacades = flowContext.getRootGroup().getProcessors();
+ if (!processorFacades.isEmpty()) {
+ final ProcessorFacade processorFacade = processorFacades.iterator().next();
+
+ try {
+ @SuppressWarnings("unchecked")
+ final List<String> fileValues = (List<String>) processorFacade.invokeConnectorMethod("getFileValues", Map.of());
+
+ return fileValues.stream()
+ .map(AllowableValue::new)
+ .toList();
+ } catch (final InvocationFailedException e) {
+ throw new RuntimeException("Failed to fetch allowable values from connector.", e);
+ }
+ }
+ }
+
+ return super.fetchAllowableValues(stepName, propertyName, flowContext);
+ }
+
public static class SimpleFileExistsValidator implements Validator {
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicFlowConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicFlowConnector.java
index 9c88d17836..bf81482143 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicFlowConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/DynamicFlowConnector.java
@@ -116,7 +116,7 @@ public class DynamicFlowConnector extends AbstractConnector {
);
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return configurationSteps;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/ParameterConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/ParameterConnector.java
index 60061fb4cc..bbc071aca0 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/ParameterConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/ParameterConnector.java
@@ -77,7 +77,7 @@ public class ParameterConnector extends AbstractConnector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of(TEXT_STEP);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
index cc43b022be..6443523e43 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/SleepingConnector.java
@@ -73,7 +73,7 @@ public class SleepingConnector implements Connector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext fLowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
index 6e9a437aa9..abdbfd8f7f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/StandardConnectorNodeIT.java
@@ -19,7 +19,6 @@ package org.apache.nifi.components.connector;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.components.DescribedValue;
-import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.connector.processors.CreateDummyFlowFile;
import org.apache.nifi.components.connector.processors.DuplicateFlowFile;
import org.apache.nifi.components.connector.processors.LogFlowFileContents;
@@ -369,7 +368,7 @@ public class StandardConnectorNodeIT {
SystemBundle.SYSTEM_BUNDLE_COORDINATE, true, true);
assertNotNull(connectorNode);
- assertEquals(List.of("File"), getConfigurationStepNames(connectorNode));
+ assertEquals(List.of("File", "Colors"), getConfigurationStepNames(connectorNode));
final Path tempFile = Files.createTempFile("StandardConnectorNodeIT", ".txt");
Files.writeString(tempFile, String.join("\n", "red", "blue", "yellow"));
@@ -381,16 +380,7 @@ public class StandardConnectorNodeIT {
connectorNode.prepareForUpdate();
assertEquals(List.of("File", "Colors"), getConfigurationStepNames(connectorNode));
- final ConfigurationStep colorConfigurationStep = connectorNode.getConfigurationSteps().stream()
- .filter(step -> step.getName().equals("Colors"))
- .findFirst()
- .orElseThrow();
-
- assertNotNull(colorConfigurationStep);
- assertEquals(1, colorConfigurationStep.getPropertyGroups().size());
- final ConnectorPropertyGroup primaryColorsPropertyGroup = colorConfigurationStep.getPropertyGroups().getFirst();
-
- final List<String> allowableValues = primaryColorsPropertyGroup.getProperties().getFirst().getAllowableValues().stream()
+ final List<String> allowableValues = connectorNode.fetchAllowableValues("Colors", "First Primary Color").stream()
.map(DescribedValue::getValue)
.toList();
assertEquals(List.of("red", "blue", "yellow"), allowableValues);
@@ -402,7 +392,7 @@ public class StandardConnectorNodeIT {
SystemBundle.SYSTEM_BUNDLE_COORDINATE, true, true);
assertNotNull(connectorNode);
- assertEquals(List.of("File"), getConfigurationStepNames(connectorNode));
+ assertEquals(List.of("File", "Colors"), getConfigurationStepNames(connectorNode));
final ConnectorConfiguration configuration = createFileConfiguration("/non/existent/file");
configure(connectorNode, configuration);
@@ -410,12 +400,17 @@ public class StandardConnectorNodeIT {
final ValidationState validationState = connectorNode.performValidation();
assertNotNull(validationState);
assertEquals(ValidationStatus.INVALID, validationState.getStatus());
- assertEquals(1, validationState.getValidationErrors().size());
+ assertEquals(2, validationState.getValidationErrors().size());
- final ValidationResult result = validationState.getValidationErrors().iterator().next();
- result.getExplanation().contains("/non/existent/file");
+ final boolean hasFileError = validationState.getValidationErrors().stream()
+ .anyMatch(result -> result.getInput() != null && result.getInput().contains("/non/existent/file"));
+ assertTrue(hasFileError);
- final ConnectorConfiguration validConfig = createFileConfiguration(".");
+ final boolean hasColorError = validationState.getValidationErrors().stream()
+ .anyMatch(result -> result.getSubject() != null && result.getSubject().contains("First Primary Color"));
+ assertTrue(hasColorError);
+
+ final ConnectorConfiguration validConfig = createFileAndColorsConfiguration(".", "red");
configure(connectorNode, validConfig);
final ValidationState updatedValidationState = connectorNode.performValidation();
@@ -523,10 +518,19 @@ public class StandardConnectorNodeIT {
}
private ConnectorConfiguration createFileConfiguration(final String filename) {
- // File configuration step
final StepConfiguration fileStepConfig = new StepConfiguration(Map.of("File Path", new StringLiteralValue(filename)));
final NamedStepConfiguration fileConfigurationStepConfiguration = new NamedStepConfiguration("File", fileStepConfig);
return new ConnectorConfiguration(Set.of(fileConfigurationStepConfiguration));
}
+
+ private ConnectorConfiguration createFileAndColorsConfiguration(final String filename, final String color) {
+ final StepConfiguration fileStepConfig = new StepConfiguration(Map.of("File Path", new StringLiteralValue(filename)));
+ final NamedStepConfiguration fileConfigurationStepConfiguration = new NamedStepConfiguration("File", fileStepConfig);
+
+ final StepConfiguration colorsStepConfig = new StepConfiguration(Map.of("First Primary Color", new StringLiteralValue(color)));
+ final NamedStepConfiguration colorsConfigurationStepConfiguration = new NamedStepConfiguration("Colors", colorsStepConfig);
+
+ return new ConnectorConfiguration(Set.of(fileConfigurationStepConfiguration, colorsConfigurationStepConfiguration));
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index 8b0b619cf5..03da830e03 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -594,7 +594,7 @@ public class TestStandardConnectorNode {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of();
}
@@ -647,7 +647,7 @@ public class TestStandardConnectorNode {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
final ConfigurationStep testStep = new ConfigurationStep.Builder()
.name("testStep")
.build();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
index b8f6c30aaf..b4b7c7d9be 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/flow/NopConnector.java
@@ -92,7 +92,7 @@ public class NopConnector implements Connector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of(new ConfigurationStep.Builder()
.name("Test Group")
.description("A test configuration step")
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
index c824f8be67..61738f428a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/DummyConnector.java
@@ -61,7 +61,7 @@ public class DummyConnector implements Connector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of();
}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/AssetConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/AssetConnector.java
index cbab02f64c..2e927bb038 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/AssetConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/AssetConnector.java
@@ -69,7 +69,7 @@ public class AssetConnector extends AbstractConnector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return List.of(ASSET_CONFIGURATION_STEP);
}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java
index 5d927096db..702a4574ad 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/connectors/tests/system/NopConnector.java
@@ -98,7 +98,7 @@ public class NopConnector extends AbstractConnector {
}
@Override
- public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ public List<ConfigurationStep> getConfigurationSteps() {
return configurationSteps;
}
(nifi) 12/40: NIFI-15258: Enable Custom UI to retrieve connector with flow contexts (#10652)
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 7f44f3a9d7d804ddbf91cdb8d054b89c30e0009c
Author: Bob Paulin <bo...@bobpaulin.com>
AuthorDate: Tue Dec 16 15:12:59 2025 -0600
NIFI-15258: Enable Custom UI to retrieve connector with flow contexts (#10652)
---
.../java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java
index 0a51db60a8..67c011330a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiConnectorWebContext.java
@@ -29,12 +29,12 @@ public class StandardNiFiConnectorWebContext implements NiFiConnectorWebContext
@Override
@SuppressWarnings("unchecked")
- public <T> T getConnector(final String connectorId) throws IllegalArgumentException {
+ public <T> ConnectorWebContext<T> getConnectorWebContext(final String connectorId) throws IllegalArgumentException {
final ConnectorNode connectorNode = connectorDAO.getConnector(connectorId);
if (connectorNode == null) {
throw new IllegalArgumentException("Unable to find connector with id: " + connectorId);
}
- return (T) connectorNode.getConnector();
+ return new ConnectorWebContext<>((T) connectorNode.getConnector(), connectorNode.getWorkingFlowContext(), connectorNode.getActiveFlowContext());
}
public void setConnectorDAO(final ConnectorDAO connectorDAO) {
(nifi) 01/40: NIFI-15258: Initial implementation of Connectors
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
markap14 pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 9d87f0947b4558413fa2602fe75ef06edb5482fd
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Tue Sep 2 18:22:14 2025 -0400
NIFI-15258: Initial implementation of Connectors
---
.cursorrules | 71 +
.../c2/command/DefaultFlowStateStrategyTest.java | 8 +-
nifi-commons/nifi-connector-utils/pom.xml | 42 +
.../connector/util/VersionedFlowUtils.java | 366 ++++
.../java/org/apache/nifi/util/NiFiProperties.java | 54 +-
nifi-commons/pom.xml | 1 +
.../nifi-connector-mock-api/pom.xml | 35 +
.../server/ConnectorConfigVerificationResult.java | 21 +-
.../mock/connector/server/ConnectorMockServer.java | 23 +-
.../mock/connector/server/ConnectorTestRunner.java | 52 +
.../nifi-connector-mock-server-nar/pom.xml | 55 +
.../nifi-connector-mock-server/pom.xml | 73 +
.../mock/connector/server/MockAuditService.java | 63 +
.../server/MockConnectorInitializationContext.java | 156 ++
.../connector/server/MockConnectorRepository.java | 25 +-
.../server/MockExtensionDiscoveringManager.java | 60 +
.../mock/connector/server/MockExtensionMapper.java | 44 +
.../server/MockRuleViolationsManager.java | 79 +
.../server/MockServerConfigVerificationResult.java | 52 +
.../mock/connector/server/MockStateManager.java | 89 +
.../connector/server/MockStateManagerProvider.java | 59 +
.../nifi/mock/connector/server/MockStateMap.java | 56 +
.../connector/server/NopPropertyEncryptor.java | 26 +-
.../mock/connector/server/PermitAllAuthorizer.java | 46 +
.../server/StandardConnectorMockServer.java | 304 +++
.../META-INF/services/org.apache.nifi.NiFiServer | 5 +-
...e.nifi.components.connector.ConnectorRepository | 5 +-
.../server/MockStateManagerProviderTest.java | 267 +++
.../nifi-connector-mock-integration-tests/pom.xml | 118 ++
.../mock/connectors/tests/CreateConnectorIT.java | 30 +-
.../mock/connectors/tests/MockProcessorIT.java | 91 +
.../pom.xml | 35 +
.../nifi-connector-mock-test-connectors/pom.xml | 48 +
.../nifi/mock/connectors/GenerateAndLog.java | 56 +
.../org.apache.nifi.components.connector.Connector | 5 +-
.../main/resources/flows/Generate_and_Update.json | 1 +
.../nifi-connector-mock-test-bundle/pom.xml | 33 +
.../nifi-connector-mock/pom.xml | 147 ++
.../connector/StandardConnectorTestRunner.java | 193 ++
.../src/main/resources/nifi.properties | 159 ++
nifi-connector-mock-bundle/pom.xml | 34 +
.../nifi-kafka-to-s3-connector/pom.xml | 40 +
.../connectors/kafkas3/KafkaConnectionStep.java | 135 ++
.../apache/nifi/connectors/kafkas3/KafkaToS3.java | 295 +++
.../connectors/kafkas3/KafkaToS3FlowBuilder.java | 217 ++
.../nifi/connectors/kafkas3/KafkaTopicsStep.java | 85 +
.../org/apache/nifi/connectors/kafkas3/S3Step.java | 156 ++
.../org.apache.nifi.components.connector.Connector | 5 +-
.../src/main/resources/flows/Kafka_to_S3.json | 2105 ++++++++++++++++++++
.../nifi-kafka-to-s3-integration-tests/pom.xml | 226 +++
.../nifi/connectors/kafkas3/KafkaToS3IT.java | 742 +++++++
.../nifi-kafka-to-s3-nar/pom.xml | 36 +
nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml | 39 +
nifi-connectors/pom.xml | 42 +
.../processors/aws/s3/AbstractS3Processor.java | 12 +
.../nifi/processors/aws/region/RegionUtil.java | 2 +-
.../apache/nifi/kafka/processors/ConsumeKafka.java | 118 +-
.../nifi/kafka/processors/ConsumeKafkaTest.java | 23 +-
.../kafka/service/api/consumer/PollingContext.java | 4 +-
.../kafka/service/Kafka3ConnectionService.java | 40 +-
.../service/consumer/Kafka3AssignmentService.java | 93 +
.../service/consumer/Kafka3ConsumerService.java | 57 -
.../kafka/service/consumer/RecordIterable.java | 79 +
.../processors/standard/DuplicateFlowFile.java | 2 +-
.../nifi/controller/queue/DropFlowFileStatus.java | 7 +
.../TestVolatileProvenanceRepository.java | 6 +-
.../nifi/authorization/resource/ResourceType.java | 3 +-
.../ClusteredConnectorRequestReplicator.java | 87 +
.../ConnectorRequestReplicatorConfiguration.java | 46 +
.../FrameworkClusterConfiguration.java | 9 +-
.../TestThreadPoolRequestReplicator.java | 6 +-
.../nifi/controller/StandardProcessorNode.java | 102 +-
.../nifi/controller/flow/AbstractFlowManager.java | 2 +-
.../controller/flowanalysis/FlowAnalysisUtil.java | 6 +-
.../repository/StandardProcessSession.java | 31 +-
...StandardControllerServiceInvocationHandler.java | 40 +-
.../service/StandardControllerServiceNode.java | 92 +-
.../service/StandardControllerServiceProvider.java | 4 +-
.../service/StandardControllerServiceResolver.java | 6 +-
.../nifi/events/VolatileBulletinRepository.java | 1 +
.../StandardVersionedComponentSynchronizer.java | 34 +-
.../apache/nifi/groups/StandardProcessGroup.java | 57 +-
...rLogObserver.java => ConnectorLogObserver.java} | 21 +-
.../apache/nifi/logging/ProcessorLogObserver.java | 2 +-
.../StandardParameterReferenceManager.java | 17 +-
.../nifi/processor/StandardValidationContext.java | 28 +-
.../StandardValidationContextFactory.java | 6 +-
.../flow/FlowAnalyzingRegistryClientNode.java | 13 +-
...pper.java => VersionedComponentFlowMapper.java} | 100 +-
.../nifi/remote/StandardRemoteProcessGroup.java | 12 +-
.../repository/StandardProcessSessionTest.java | 4 +
.../StandardControllerServiceResolverTest.java | 6 +-
.../flow/FlowAnalyzingRegistryClientNodeTest.java | 4 +-
....java => TestVersionedComponentFlowMapper.java} | 10 +-
.../connector/ConfigurationStepConfiguration.java} | 19 +-
.../connector/ConfigurationUpdateResult.java} | 20 +-
.../connector/ConnectorConfiguration.java | 57 +
.../nifi/components/connector/ConnectorNode.java | 211 ++
.../components/connector/ConnectorRepository.java | 95 +
.../ConnectorRepositoryInitializationContext.java} | 24 +-
.../connector/ConnectorRequestReplicator.java} | 20 +-
.../connector/ConnectorState.java} | 28 +-
.../connector/ConnectorStateTransition.java | 82 +
.../connector/FlowContextFactory.java} | 21 +-
.../FrameworkConnectorInitializationContext.java} | 18 +-
...workConnectorInitializationContextBuilder.java} | 26 +-
.../connector/FrameworkFlowContext.java} | 23 +-
.../MutableConnectorConfigurationContext.java | 58 +
.../connector/ParameterContextFacadeFactory.java} | 21 +-
.../connector/ProcessGroupFacadeFactory.java} | 22 +-
.../connector/ProcessGroupFactory.java} | 20 +-
.../DisabledServiceValidationResult.java | 14 +
.../org/apache/nifi/connectable/Connectable.java | 2 +
.../connectable/ConnectableFlowFileActivity.java | 70 +
.../FlowFileActivity.java} | 24 +-
.../nifi/connectable/FlowFileTransferCounts.java | 88 +
.../connectable/ProcessGroupFlowFileActivity.java | 149 ++
.../nifi/controller/AbstractComponentNode.java | 48 +-
.../org/apache/nifi/controller/AbstractPort.java | 10 +
.../org/apache/nifi/controller/ComponentNode.java | 3 +
.../apache/nifi/controller/ProcessScheduler.java | 15 +-
.../org/apache/nifi/controller/ProcessorNode.java | 17 +-
.../org/apache/nifi/controller/StandardFunnel.java | 9 +
.../nifi/controller/ValidationContextFactory.java | 4 +-
.../apache/nifi/controller/flow/FlowManager.java | 17 +
.../nifi/controller/flow/VersionedDataflow.java | 10 +
.../nifi/controller/queue/DropFlowFileRequest.java | 16 +
.../controller/service/ControllerServiceNode.java | 10 +-
.../service/ControllerServiceProvider.java | 4 +-
.../org/apache/nifi/events/BulletinFactory.java | 7 +
.../java/org/apache/nifi/groups/ProcessGroup.java | 26 +-
.../org/apache/nifi/groups/RemoteProcessGroup.java | 4 +-
.../nifi-framework/nifi-framework-core/pom.xml | 6 +
.../components/connector/ConnectorDetails.java} | 33 +-
.../connector/ConnectorParameterLookup.java | 176 ++
.../ConnectorValidationContextBridge.java | 134 ++
.../connector/DescribedValueProvider.java} | 18 +-
.../nifi/components/connector/GhostConnector.java | 128 ++
.../StandaloneConnectorRequestReplicator.java} | 30 +-
.../StandardConnectorConfigurationContext.java | 328 +++
.../StandardConnectorInitializationContext.java | 122 ++
.../connector/StandardConnectorNode.java | 805 ++++++++
.../connector/StandardConnectorPropertyValue.java | 101 +
...StandardConnectorRepoInitializationContext.java | 59 +
.../connector/StandardConnectorRepository.java | 187 ++
.../StandardConnectorStateTransition.java | 113 ++
.../StandardConnectorValidationContext.java | 113 ++
.../components/connector/StandardFlowContext.java | 119 ++
.../standalone/ComponentContextProvider.java | 41 +
...egalExecutionEngineStatelessGroupLifecycle.java | 46 +
.../standalone/StandaloneConnectionFacade.java | 49 +
.../StandaloneControllerServiceFacade.java | 172 ++
.../StandaloneControllerServiceLifecycle.java | 62 +
.../StandaloneParameterContextFacade.java | 208 ++
.../standalone/StandaloneProcessGroupFacade.java | 380 ++++
.../StandaloneProcessGroupLifecycle.java | 294 +++
.../standalone/StandaloneProcessorFacade.java | 180 ++
.../standalone/StandaloneProcessorLifecycle.java | 93 +
.../StandaloneStatelessGroupLifecycle.java | 51 +
.../apache/nifi/controller/ExtensionBuilder.java | 343 ++--
.../org/apache/nifi/controller/FlowController.java | 75 +-
.../flow/FlowControllerFlowContextFactory.java | 114 ++
.../FlowControllerProcessGroupFacadeFactory.java | 70 +
.../flow/StandardComponentContextProvider.java | 105 +
.../nifi/controller/flow/StandardFlowManager.java | 151 +-
.../flow/StandardStatelessGroupNodeFactory.java | 4 +-
.../flowanalysis/StandardFlowAnalysisContext.java | 4 +-
.../scheduling/StandardProcessScheduler.java | 89 +-
.../serialization/VersionedDataflowMapper.java | 22 +-
.../serialization/VersionedFlowSynchronizer.java | 184 ++
.../VersionedReportingTaskSnapshotMapper.java | 6 +-
.../nifi/flowanalysis/StandardFlowAnalyzer.java | 10 +-
.../configuration/FlowControllerConfiguration.java | 78 +-
.../nifi/groups/StandardStatelessGroupNode.java | 9 +
....nifi.components.connector.ConnectorRepository} | 5 +-
.../components/connector/BlockingConnector.java | 126 ++
.../connector/DynamicAllowableValuesConnector.java | 137 ++
.../components/connector/DynamicFlowConnector.java | 335 ++++
.../components/connector/ParameterConnector.java | 130 ++
.../components/connector/SleepingConnector.java | 120 ++
.../connector/StandardConnectorNodeIT.java | 540 +++++
.../TestStandardConnectorConfigurationContext.java | 299 +++
...TestStandardConnectorInitializationContext.java | 128 ++
.../connector/TestStandardConnectorNode.java | 638 ++++++
.../connector/TestStandardConnectorRepository.java | 122 ++
.../connector/processors/CreateDummyFlowFile.java | 84 +
.../connector/processors/DuplicateFlowFile.java | 128 ++
.../connector/processors/ExposeFileValues.java | 72 +
.../connector/processors/LogFlowFileContents.java | 58 +
.../connector/processors/OverwriteFlowFile.java | 79 +-
.../components/connector/processors/Sleep.java | 151 ++
.../connector/processors/TerminateFlowFile.java} | 31 +-
.../connector/services/CounterService.java} | 20 +-
.../services/impl/StandardCounterService.java} | 27 +-
.../nifi/controller/MockStateManagerProvider.java | 70 +
.../apache/nifi/controller/flow/NopConnector.java | 162 ++
.../controller/flow/TestStandardFlowManager.java | 166 ++
.../TestStandardControllerServiceProvider.java | 41 +-
.../controller/service/mock/MockProcessGroup.java | 19 +
....java => VersionedComponentFlowMapperTest.java} | 4 +-
...org.apache.nifi.components.connector.Connector} | 7 +-
.../org.apache.nifi.controller.ControllerService | 5 +-
.../services/org.apache.nifi.processor.Processor | 9 +-
.../src/test/resources/flows/choose-color.json | 76 +
.../flows/generate-and-log-with-parameter.json | 253 +++
.../flows/generate-duplicate-log-flow.json | 690 +++++++
.../nar/StandardExtensionDiscoveringManager.java | 20 +-
.../java/org/apache/nifi/nar/DummyConnector.java | 114 ++
...rdExtensionDiscoveringManagerConnectorTest.java | 49 +
...org.apache.nifi.components.connector.Connector} | 7 +-
.../apache/nifi/headless/HeadlessNiFiServer.java | 36 +-
.../main/java/org/apache/nifi/nar/NarUnpacker.java | 4 +-
.../web/server/StandardServerProviderTest.java | 8 +-
.../nifi/authorization/AuthorizableLookup.java | 6 +
.../authorization/StandardAuthorizableLookup.java | 7 +
.../apache/nifi/web/StandardNiFiServiceFacade.java | 38 +-
.../web/dao/impl/StandardControllerServiceDAO.java | 2 +-
.../nifi/web/dao/impl/StandardProcessGroupDAO.java | 4 +-
.../nifi/web/dao/impl/StandardProcessorDAO.java | 3 +-
.../nifi/web/StandardNiFiServiceFacadeTest.java | 10 +-
.../nifi/web/api/TestDataTransferResource.java | 18 +-
.../dao/impl/TestStandardParameterContextDAO.java | 2 +-
.../StandardClientRegistrationProviderTest.java | 2 +-
.../StandardRegistrationBuilderProviderTest.java | 4 +-
...dardRelyingPartyRegistrationRepositoryTest.java | 4 +-
.../x509/X509AuthenticationProviderTest.java | 2 +-
.../java/org/apache/nifi/nar/ExtensionMapping.java | 25 +-
.../stateless/bootstrap/AllowListClassLoader.java | 5 +
.../stateless/bootstrap/StatelessBootstrap.java | 2 +-
.../scheduling/StatelessProcessScheduler.java | 29 +-
.../stateless/engine/StatelessFlowManager.java | 16 +
pom.xml | 4 +-
232 files changed, 19206 insertions(+), 973 deletions(-)
diff --git a/.cursorrules b/.cursorrules
new file mode 100644
index 0000000000..3bcdd1f6cd
--- /dev/null
+++ b/.cursorrules
@@ -0,0 +1,71 @@
+AI persona
+----------
+Act as an experienced Java software engineer. When considering how to implement a task, first consider the big picture of what is being asked. Then determine which classes will need to be udpated.
+Quite often, a single request will require manipulating many different classes. Generally speaking, it is best to avoid changing established interfaces, but it is often acceptable because the public API
+that NiFi adheres to is not in this module.
+
+
+API
+---
+When it is necessary to lookup the API for reference, the API can generally be found at ../nifi-api, where .. is relative to the project's root directory.
+
+Building
+--------
+NiFi is a complex Maven codebase. Never build code (testing or otherwise) using javac. Always use `mvn` instead.
+
+Code Style
+----------
+NiFi adheres to a few code styles that are not necessarily common. Please ensure that you observe these code styles.
+1. Any variable that can be marked `final` must be marked `final`. This includes declarations of Exceptions, method arguments, local variables, member variables, etc.
+2. Short-hand is highly discouraged in names of variables, classes, methods, etc., as well as in documentation.
+3. Private / helper methods should not be placed before the first public/protected method that calls it.
+4. Unless the method is to be heavily reused, avoid creating trivial 1-2 line methods and instead just place the code inline.
+5. Code is allowed to be up to 200 characters wide. Avoid breaking lines into many short lines.
+6. Avoid creating private methods that are called only once unless they are at least 10 lines long or are complex.
+7. It is never acceptable to use star imports. Import each individual class that is to be used.
+8. Never use underscores in class names, variables, or filenames.
+9. Never use System.out.println but instead use SLF4J Loggers
+10. Avoid excessive whitespace in method invocations. For example, instead of writing:
+```
+myObject.doSomething(
+ arg1,
+ arg2,
+ arg3,
+ arg4,
+ arg5
+);
+```
+Write this instead:
+```
+myObject.doSomething(arg1, arg2, arg3, arg4, arg5);
+```
+
+11. When possible, prefer importing a class, rather than using fully qualified classname inline in the code.
+12. Avoid statically importing methods unless they are used frequently such as methods in the `Assertions` and `Mockito` classes.
+13. Avoid trailing whitespace at the end of lines, especially in blank lines.
+
+Unit / Automated Testing
+------------------------
+In addition to the general rules defined in the Code Style section, follow these rules when creating or manipulating automated tests.
+1. NEVER add comments such as `// Given`, `// When`, `// Then`. These comments are considered an anti-pattern and should be removed or replaced whenever they are encountered.
+ Instead, leave them out all together (preferred) or use comments that clearly articulate what is happening, such as `// Setup`, `// Invoke method`, `// Assert expected results.`
+ One of the reasons that this is considered an anti-pattern (in addition to the fact that the given/when/then nomeclature itself provides no meaning) is that it assumes a very specific pattern
+ in unit tests, that we will create a bunch of objects, invoke the method we care about, make assertions, and then end. This often results in many tests that are extremely repetitive.
+ Instead, whenever it makes sense to do so, create the prerequisitive objects, invoke the method we care about with appropriate arguments, make assertions, and then invoke again with a different
+ set of arguments, make assertions, etc. There is no need to have many repetitive methods that each create many repetitive objects.
+
+2. Unit tests are Java. They are not English. As such, they should be written like Java. Frameworks such as assertj that strive to make the unit tests look more "English-like" should be avoided.
+ Use of these frameworks sometimes works well but often quickly devolves into automated tests that read like neither English nor Java.
+
+3. Like any other code, unit tests should be created using reusable methods where appropriate. Do not create 15 methods that are all very similar and repetitive. Instead, create reusable methods
+ that can be called from each of the methods.
+
+4. Never use the `assert` keyword. Use JUnit assertions instead.
+
+5. Never create a test file that is centered around testing a method or capability. Unit tests must always be named after the class they are testing. It is okay if a given unit test class is very, very long.
+
+6. This is a Java project using the Maven structure. Java test files must always fall under src/test/java of the appropriate sub-module.
+
+7. Never use pointless assertions such as assertDoesNotThrow - this adds nothing but complexity. Just call the method, and if it throws an Exception, the test will fail. I.e.,
+ it is assumed by default that each line does not throw an Exception, so do not use
+
diff --git a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/test/java/org/apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/test/java/org/apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java
index ad23f6204a..6ba9ea9221 100644
--- a/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/test/java/org/apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java
+++ b/minifi/minifi-nar-bundles/minifi-framework-bundle/minifi-framework/minifi-framework-core/src/test/java/org/apache/nifi/minifi/c2/command/DefaultFlowStateStrategyTest.java
@@ -111,13 +111,13 @@ public class DefaultFlowStateStrategyTest {
ProcessGroup rootProcessGroup = mock(ProcessGroup.class);
RemoteProcessGroup remoteProcessGroup = mock(RemoteProcessGroup.class);
Set<RemoteProcessGroup> remoteProcessGroups = Set.of(remoteProcessGroup);
- CompletableFuture<Void> rootProcessGroupStopFuture = mock(CompletableFuture.class);
- Future remoteProcessGroupStopFuture = mock(Future.class);
+ CompletableFuture<Void> rootProcessGroupStopFuture = CompletableFuture.completedFuture(null);
+ CompletableFuture<Void> remoteProcessGroupStopFuture = CompletableFuture.completedFuture(null);
ProcessGroup nestedProcessGroup = mock(ProcessGroup.class);
RemoteProcessGroup nestedRemoteProcessGroup = mock(RemoteProcessGroup.class);
Set<RemoteProcessGroup> nestedRemoteProcessGroups = Set.of(nestedRemoteProcessGroup);
- CompletableFuture<Void> nestedProcessGroupStopFuture = mock(CompletableFuture.class);
- Future nestedRemoteProcessGroupStopFuture = mock(Future.class);
+ CompletableFuture<Void> nestedProcessGroupStopFuture = CompletableFuture.completedFuture(null);
+ CompletableFuture<Void> nestedRemoteProcessGroupStopFuture = CompletableFuture.completedFuture(null);
when(flowManager.getRootGroup()).thenReturn(rootProcessGroup);
when(rootProcessGroup.getRemoteProcessGroups()).thenReturn(remoteProcessGroups);
diff --git a/nifi-commons/nifi-connector-utils/pom.xml b/nifi-commons/nifi-connector-utils/pom.xml
new file mode 100644
index 0000000000..2794ecbfd6
--- /dev/null
+++ b/nifi-commons/nifi-connector-utils/pom.xml
@@ -0,0 +1,42 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <!--
+ 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.
+ -->
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-commons</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-utils</artifactId>
+ <name>nifi-connector-utils</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java b/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java
new file mode 100644
index 0000000000..8eb839a3ba
--- /dev/null
+++ b/nifi-commons/nifi-connector-utils/src/main/java/org/apache/nifi/components/connector/util/VersionedFlowUtils.java
@@ -0,0 +1,366 @@
+/*
+ * 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.components.connector.util;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.flow.ComponentType;
+import org.apache.nifi.flow.ConnectableComponent;
+import org.apache.nifi.flow.ConnectableComponentType;
+import org.apache.nifi.flow.Position;
+import org.apache.nifi.flow.ScheduledState;
+import org.apache.nifi.flow.VersionedConnection;
+import org.apache.nifi.flow.VersionedControllerService;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedParameter;
+import org.apache.nifi.flow.VersionedParameterContext;
+import org.apache.nifi.flow.VersionedPort;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Predicate;
+
+public class VersionedFlowUtils {
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ public static VersionedExternalFlow loadFlowFromResource(final String resourceName) {
+ try (final InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
+ if (in == null) {
+ throw new IllegalArgumentException("Resource not found: " + resourceName);
+ }
+
+ return OBJECT_MAPPER.readValue(in, VersionedExternalFlow.class);
+ } catch (final Exception e) {
+ throw new IllegalStateException("Unable to load resource: " + resourceName, e);
+ }
+ }
+
+ public static Optional<VersionedProcessor> findProcessor(final VersionedProcessGroup group, final Predicate<VersionedProcessor> predicate) {
+ final List<VersionedProcessor> processors = findProcessors(group, predicate);
+ if (processors.size() == 1) {
+ return Optional.of(processors.getFirst());
+ }
+ return Optional.empty();
+ }
+
+ public static List<VersionedProcessor> findProcessors(final VersionedProcessGroup group, final Predicate<VersionedProcessor> predicate) {
+ final List<VersionedProcessor> processors = new ArrayList<>();
+ findProcessors(group, predicate, processors);
+ return processors;
+ }
+
+ private static void findProcessors(final VersionedProcessGroup group, final Predicate<VersionedProcessor> predicate, final List<VersionedProcessor> processors) {
+ for (final VersionedProcessor processor : group.getProcessors()) {
+ if (predicate.test(processor)) {
+ processors.add(processor);
+ }
+ }
+
+ for (final VersionedProcessGroup childGroup : group.getProcessGroups()) {
+ findProcessors(childGroup, predicate, processors);
+ }
+ }
+
+ public static ConnectableComponent createConnectableComponent(final VersionedProcessor processor) {
+ final ConnectableComponent component = new ConnectableComponent();
+ component.setId(processor.getIdentifier());
+ component.setName(processor.getName());
+ component.setType(ConnectableComponentType.PROCESSOR);
+ component.setGroupId(processor.getGroupIdentifier());
+ return component;
+ }
+
+ public static ConnectableComponent createConnectableComponent(final VersionedPort port) {
+ final ConnectableComponent component = new ConnectableComponent();
+ component.setId(port.getIdentifier());
+ component.setName(port.getName());
+ component.setType(port.getComponentType() == ComponentType.INPUT_PORT ? ConnectableComponentType.INPUT_PORT : ConnectableComponentType.OUTPUT_PORT);
+ component.setGroupId(port.getGroupIdentifier());
+ return component;
+ }
+
+ public static void addConnection(final VersionedProcessGroup group, final ConnectableComponent source, final ConnectableComponent destination, final Set<String> relationships) {
+ final VersionedConnection connection = new VersionedConnection();
+ connection.setSource(source);
+ connection.setDestination(destination);
+ connection.setSelectedRelationships(relationships);
+ connection.setBends(List.of());
+ connection.setLabelIndex(0);
+ connection.setzIndex(0L);
+ connection.setGroupIdentifier(group.getIdentifier());
+ connection.setLoadBalanceStrategy("DO_NOT_LOAD_BALANCE");
+ connection.setBackPressureDataSizeThreshold("1 GB");
+ connection.setBackPressureObjectThreshold(10000L);
+ connection.setFlowFileExpiration("0 sec");
+ connection.setPrioritizers(new ArrayList<>());
+ connection.setComponentType(ComponentType.CONNECTION);
+
+ Set<VersionedConnection> connections = group.getConnections();
+ if (connections == null) {
+ connections = new HashSet<>();
+ group.setConnections(connections);
+ }
+ connections.add(connection);
+
+ final String uuid = generateDeterministicUuid(group, ComponentType.CONNECTION);
+ connection.setIdentifier(uuid);
+ }
+
+ public static List<VersionedConnection> findOutboundConnections(final VersionedProcessGroup group, final VersionedProcessor processor) {
+ final VersionedProcessGroup processorGroup = findGroupForProcessor(group, processor);
+ if (processorGroup == null) {
+ return List.of();
+ }
+
+ final List<VersionedConnection> outboundConnections = new ArrayList<>();
+ final Set<VersionedConnection> connections = processorGroup.getConnections();
+ if (connections == null) {
+ return outboundConnections;
+ }
+
+ for (final VersionedConnection connection : connections) {
+ final ConnectableComponent source = connection.getSource();
+ if (Objects.equals(source.getId(), processor.getIdentifier()) && source.getType() == ConnectableComponentType.PROCESSOR) {
+ outboundConnections.add(connection);
+ }
+ }
+
+ return outboundConnections;
+ }
+
+ public static VersionedProcessGroup findGroupForProcessor(final VersionedProcessGroup rootGroup, final VersionedProcessor processor) {
+ if (rootGroup.getProcessors().contains(processor)) {
+ return rootGroup;
+ }
+
+ for (final VersionedProcessGroup childGroup : rootGroup.getProcessGroups()) {
+ final VersionedProcessGroup foundGroup = findGroupForProcessor(childGroup, processor);
+ if (foundGroup != null) {
+ return foundGroup;
+ }
+ }
+
+ return null;
+ }
+
+ public static String generateDeterministicUuid(final VersionedProcessGroup group, final ComponentType componentType) {
+ final int componentCount = getComponentCount(group, componentType);
+ final String uuidSeed = "%s-%s-%d".formatted(group.getIdentifier(), componentType.name(), componentCount);
+ return UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8)).toString();
+ }
+
+ private static int getComponentCount(final VersionedProcessGroup group, final ComponentType componentType) {
+ final Collection<?> components = switch (componentType) {
+ case PROCESSOR -> group.getProcessors();
+ case INPUT_PORT -> group.getInputPorts();
+ case OUTPUT_PORT -> group.getOutputPorts();
+ case CONNECTION -> group.getConnections();
+ case FUNNEL -> group.getFunnels();
+ case LABEL -> group.getLabels();
+ case PROCESS_GROUP -> group.getProcessGroups();
+ case CONTROLLER_SERVICE -> group.getControllerServices();
+ default -> List.of();
+ };
+
+ return components == null ? 0 : components.size();
+ }
+
+ public static VersionedProcessor addProcessor(final VersionedProcessGroup group, final String processorType, final Bundle bundle, final String name, final Position position) {
+ final VersionedProcessor processor = new VersionedProcessor();
+
+ // Generate deterministic UUID based on group and component type
+ processor.setIdentifier(generateDeterministicUuid(group, ComponentType.PROCESSOR));
+
+ processor.setName(name);
+ processor.setType(processorType);
+ processor.setPosition(position);
+ processor.setBundle(bundle);
+
+ // Set default processor configuration
+ processor.setProperties(new HashMap<>());
+ processor.setPropertyDescriptors(new HashMap<>());
+ processor.setStyle(new HashMap<>());
+ processor.setSchedulingPeriod("0 sec");
+ processor.setSchedulingStrategy("TIMER_DRIVEN");
+ processor.setExecutionNode("ALL");
+ processor.setPenaltyDuration("30 sec");
+ processor.setYieldDuration("1 sec");
+ processor.setBulletinLevel("WARN");
+ processor.setRunDurationMillis(25L);
+ processor.setConcurrentlySchedulableTaskCount(1);
+ processor.setAutoTerminatedRelationships(new HashSet<>());
+ processor.setScheduledState(ScheduledState.ENABLED);
+ processor.setRetryCount(10);
+ processor.setRetriedRelationships(new HashSet<>());
+ processor.setBackoffMechanism("PENALIZE_FLOWFILE");
+ processor.setMaxBackoffPeriod("10 mins");
+ processor.setComponentType(ComponentType.PROCESSOR);
+ processor.setGroupIdentifier(group.getIdentifier());
+
+ group.getProcessors().add(processor);
+ return processor;
+ }
+
+ public static VersionedControllerService addControllerService(final VersionedProcessGroup group, final String serviceType, final Bundle bundle, final String name) {
+ final VersionedControllerService controllerService = new VersionedControllerService();
+
+ // Generate deterministic UUID based on group and component type
+ controllerService.setIdentifier(generateDeterministicUuid(group, ComponentType.CONTROLLER_SERVICE));
+
+ controllerService.setName(name);
+ controllerService.setType(serviceType);
+ controllerService.setBundle(bundle);
+ controllerService.setComponentType(ComponentType.CONTROLLER_SERVICE);
+
+ // Set default controller service configuration
+ controllerService.setProperties(new HashMap<>());
+ controllerService.setPropertyDescriptors(new HashMap<>());
+ controllerService.setControllerServiceApis(new ArrayList<>());
+ controllerService.setAnnotationData(null);
+ controllerService.setScheduledState(ScheduledState.DISABLED);
+ controllerService.setBulletinLevel("WARN");
+ controllerService.setComments(null);
+ controllerService.setGroupIdentifier(group.getIdentifier());
+
+ // Initialize controller services collection if it doesn't exist
+ Set<VersionedControllerService> controllerServices = group.getControllerServices();
+ if (controllerServices == null) {
+ controllerServices = new HashSet<>();
+ group.setControllerServices(controllerServices);
+ }
+ controllerServices.add(controllerService);
+
+ return controllerService;
+ }
+
+ public static Set<VersionedControllerService> getReferencedControllerServices(final VersionedProcessGroup group) {
+ final Set<VersionedControllerService> referencedServices = new HashSet<>();
+ collectReferencedControllerServices(group, referencedServices);
+ return referencedServices;
+ }
+
+ private static void collectReferencedControllerServices(final VersionedProcessGroup group, final Set<VersionedControllerService> referencedServices) {
+ final Map<String, VersionedControllerService> serviceMap = new HashMap<>();
+ for (final VersionedControllerService service : group.getControllerServices()) {
+ serviceMap.put(service.getIdentifier(), service);
+ }
+
+ for (final VersionedProcessor processor : group.getProcessors()) {
+ for (final String propertyValue : processor.getProperties().values()) {
+ final VersionedControllerService referencedService = serviceMap.get(propertyValue);
+ if (referencedService != null) {
+ referencedServices.add(referencedService);
+ }
+ }
+ }
+
+ while (true) {
+ final Set<VersionedControllerService> newlyAddedServices = new HashSet<>();
+
+ for (final VersionedControllerService service : referencedServices) {
+ for (final String propertyValue : service.getProperties().values()) {
+ final VersionedControllerService referencedService = serviceMap.get(propertyValue);
+ if (referencedService != null && !referencedServices.contains(referencedService)) {
+ newlyAddedServices.add(referencedService);
+ }
+ }
+ }
+
+ referencedServices.addAll(newlyAddedServices);
+ if (newlyAddedServices.isEmpty()) {
+ break;
+ }
+ }
+
+ for (final VersionedProcessGroup childGroup : group.getProcessGroups()) {
+ collectReferencedControllerServices(childGroup, referencedServices);
+ }
+ }
+
+ public static void removeControllerServiceReferences(final VersionedProcessGroup processGroup, final String serviceIdentifier) {
+ for (final VersionedProcessor processor : processGroup.getProcessors()) {
+ removeValuesFromMap(processor.getProperties(), serviceIdentifier);
+ }
+
+ for (final VersionedControllerService service : processGroup.getControllerServices()) {
+ removeValuesFromMap(service.getProperties(), serviceIdentifier);
+ }
+
+ for (final VersionedProcessGroup childGroup : processGroup.getProcessGroups()) {
+ removeControllerServiceReferences(childGroup, serviceIdentifier);
+ }
+ }
+
+ private static void removeValuesFromMap(final Map<String, String> properties, final String valueToRemove) {
+ final List<String> keysToRemove = new ArrayList<>();
+ for (final Map.Entry<String, String> entry : properties.entrySet()) {
+ if (Objects.equals(entry.getValue(), valueToRemove)) {
+ keysToRemove.add(entry.getKey());
+ }
+ }
+
+ keysToRemove.forEach(properties::remove);
+ }
+
+ public static void setParameterValue(final VersionedExternalFlow externalFlow, final String parameterName, final String parameterValue) {
+ for (final VersionedParameterContext context : externalFlow.getParameterContexts().values()) {
+ setParameterValue(context, parameterName, parameterValue);
+ }
+ }
+
+ public static void setParameterValue(final VersionedParameterContext parameterContext, final String parameterName, final String parameterValue) {
+ final Set<VersionedParameter> parameters = parameterContext.getParameters();
+ for (final VersionedParameter parameter : parameters) {
+ if (parameter.getName().equals(parameterName)) {
+ parameter.setValue(parameterValue);
+ }
+ }
+ }
+
+ public static void removeUnreferencedControllerServices(final VersionedProcessGroup processGroup) {
+ final Set<VersionedControllerService> referencedServices = getReferencedControllerServices(processGroup);
+ final Set<String> referencedServiceIds = new HashSet<>();
+ for (final VersionedControllerService service : referencedServices) {
+ referencedServiceIds.add(service.getIdentifier());
+ }
+
+ removeUnreferencedControllerServices(processGroup, referencedServiceIds);
+ }
+
+ private static void removeUnreferencedControllerServices(final VersionedProcessGroup processGroup, final Set<String> referencedServiceIds) {
+ processGroup.getControllerServices().removeIf(service -> !referencedServiceIds.contains(service.getIdentifier()));
+
+ for (final VersionedProcessGroup childGroup : processGroup.getProcessGroups()) {
+ removeUnreferencedControllerServices(childGroup, referencedServiceIds);
+ }
+ }
+}
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 86e83526ce..e70e34036b 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -136,6 +136,9 @@ public class NiFiProperties extends ApplicationProperties {
public static final String STATUS_REPOSITORY_QUESTDB_PERSIST_BATCH_SIZE = "nifi.status.repository.questdb.persist.batchsize";
public static final String STATUS_REPOSITORY_QUESTDB_PERSIST_FREQUENCY = "nifi.status.repository.questdb.persist.frequency";
+ // Connector Repository properties
+ public static final String CONNECTOR_REPOSITORY_IMPLEMENTATION = "nifi.components.connectors.repository.implementation";
+
// security properties
public static final String SECURITY_KEYSTORE = "nifi.security.keystore";
public static final String SECURITY_KEYSTORE_TYPE = "nifi.security.keystoreType";
@@ -1811,8 +1814,28 @@ public class NiFiProperties extends ApplicationProperties {
// The Properties(Properties) constructor does NOT inherit the provided values, just uses them as default values
if (additionalProperties != null) {
- additionalProperties.forEach(properties::put);
+ properties.putAll(additionalProperties);
+ }
+
+ return createNiFiProperties(properties);
+ }
+
+ public static NiFiProperties createBasicNiFiProperties(final InputStream inputStream) {
+ return createBasicNiFiProperties(inputStream, null);
+ }
+
+ public static NiFiProperties createBasicNiFiProperties(final InputStream inputStream, final Properties additionalProperties) {
+ final Properties properties = new Properties();
+ readFromInputStream(inputStream, properties);
+
+ if (additionalProperties != null) {
+ properties.putAll(additionalProperties);
}
+
+ return createNiFiProperties(properties);
+ }
+
+ private static NiFiProperties createNiFiProperties(final Properties properties) {
return new NiFiProperties() {
@Override
public String getProperty(String key) {
@@ -1832,9 +1855,7 @@ public class NiFiProperties extends ApplicationProperties {
}
private static void readFromPropertiesFile(String propertiesFilePath, Properties properties) {
- final String nfPropertiesFilePath = (propertiesFilePath == null)
- ? System.getProperty(PROPERTIES_FILE_PATH)
- : propertiesFilePath;
+ final String nfPropertiesFilePath = (propertiesFilePath == null) ? System.getProperty(PROPERTIES_FILE_PATH) : propertiesFilePath;
if (nfPropertiesFilePath != null) {
final File propertiesFile = new File(nfPropertiesFilePath.trim());
if (!propertiesFile.exists()) {
@@ -1845,24 +1866,25 @@ public class NiFiProperties extends ApplicationProperties {
throw new RuntimeException("Properties file exists but cannot be read '"
+ propertiesFile.getAbsolutePath() + "'");
}
- InputStream inStream = null;
- try {
- inStream = new BufferedInputStream(new FileInputStream(propertiesFile));
+
+ try (final InputStream fileIn = new FileInputStream(propertiesFile);
+ final InputStream inStream = new BufferedInputStream(fileIn)) {
+
properties.load(inStream);
} catch (final Exception ex) {
- throw new RuntimeException("Cannot load properties file due to "
- + ex.getLocalizedMessage(), ex);
- } finally {
- if (null != inStream) {
- try {
- inStream.close();
- } catch (final Exception ignored) {
- }
- }
+ throw new RuntimeException("Cannot load properties file due to " + ex.getLocalizedMessage(), ex);
}
}
}
+ private static void readFromInputStream(final InputStream inputStream, final Properties properties) {
+ try {
+ properties.load(inputStream);
+ } catch (final Exception ex) {
+ throw new RuntimeException("Cannot load properties file due to " + ex.getLocalizedMessage(), ex);
+ }
+ }
+
/**
* This method is used to validate the NiFi properties when the file is loaded
* for the first time. The objective is to stop NiFi startup in case a property
diff --git a/nifi-commons/pom.xml b/nifi-commons/pom.xml
index 53179adcac..a4173dfe0a 100644
--- a/nifi-commons/pom.xml
+++ b/nifi-commons/pom.xml
@@ -68,6 +68,7 @@
<module>nifi-write-ahead-log</module>
<module>nifi-xml-processing</module>
<module>nifi-calcite-utils</module>
+ <module>nifi-connector-utils</module>
</modules>
<properties>
<guava.version>33.5.0-jre</guava.version>
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-api/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-api/pom.xml
new file mode 100644
index 0000000000..2d581f17da
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock-api</artifactId>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-server-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorConfigVerificationResult.java
similarity index 50%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
copy to nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorConfigVerificationResult.java
index 96806fca1d..9ec3ac2878 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorConfigVerificationResult.java
@@ -14,23 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.controller;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.ValidationContext;
-import org.apache.nifi.controller.flowanalysis.FlowAnalyzer;
-import org.apache.nifi.parameter.ParameterContext;
-import org.apache.nifi.validation.RuleViolationsManager;
+package org.apache.nifi.mock.connector.server;
-import java.util.Map;
-import java.util.Optional;
+import org.apache.nifi.components.ConfigVerificationResult;
-public interface ValidationContextFactory {
+import java.util.List;
- ValidationContext newValidationContext(Map<PropertyDescriptor, PropertyConfiguration> properties, String annotationData, String groupId, String componentId, ParameterContext parameterContext,
- boolean validateConnections);
+public interface ConnectorConfigVerificationResult {
- Optional<RuleViolationsManager> getRuleViolationsManager();
+ List<ConfigVerificationResult> getAllResults();
- Optional<FlowAnalyzer> getFlowAnalyzer();
+ List<ConfigVerificationResult> getFailedResults();
+
+ void assertNoFailures();
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorMockServer.java
similarity index 50%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
copy to nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorMockServer.java
index 96806fca1d..7b24c973c9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorMockServer.java
@@ -14,23 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.controller;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.ValidationContext;
-import org.apache.nifi.controller.flowanalysis.FlowAnalyzer;
-import org.apache.nifi.parameter.ParameterContext;
-import org.apache.nifi.validation.RuleViolationsManager;
+package org.apache.nifi.mock.connector.server;
-import java.util.Map;
-import java.util.Optional;
+import org.apache.nifi.NiFiServer;
+import org.apache.nifi.processor.Processor;
-public interface ValidationContextFactory {
+import java.io.File;
- ValidationContext newValidationContext(Map<PropertyDescriptor, PropertyConfiguration> properties, String annotationData, String groupId, String componentId, ParameterContext parameterContext,
- boolean validateConnections);
+public interface ConnectorMockServer extends NiFiServer, ConnectorTestRunner {
- Optional<RuleViolationsManager> getRuleViolationsManager();
+ void instantiateConnector(String connectorClassName);
+
+ void registerMockBundle(ClassLoader classLoader, File workingDirectory);
+
+ void mockProcessor(String processorType, Class<? extends Processor> mockProcessorClass);
- Optional<FlowAnalyzer> getFlowAnalyzer();
}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
new file mode 100644
index 0000000000..2c3eaa40ee
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-api/src/main/java/org/apache/nifi/mock/connector/server/ConnectorTestRunner.java
@@ -0,0 +1,52 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.processor.Processor;
+
+import java.io.Closeable;
+import java.time.Duration;
+import java.util.List;
+
+public interface ConnectorTestRunner extends Closeable {
+
+ void applyUpdate() throws FlowUpdateException;
+
+ default void configure(String stepName, PropertyGroupConfiguration groupConfiguration) throws FlowUpdateException {
+ configure(stepName, List.of(groupConfiguration));
+ }
+
+ void configure(String stepName, List<PropertyGroupConfiguration> groupConfigurations) throws FlowUpdateException;
+
+ ConnectorConfigVerificationResult verifyConfiguration(String stepName, List<PropertyGroupConfiguration> groupConfigurations);
+
+ void startConnector();
+
+ void stopConnector();
+
+ void waitForDataIngested(Duration maxWaitTime);
+
+ void waitForIdle(Duration maxWaitTime);
+
+ void waitForIdle(Duration minimumIdleTime, Duration maxWaitTime);
+
+ List<ValidationResult> validate();
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server-nar/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-server-nar/pom.xml
new file mode 100644
index 0000000000..eb4b1e8f72
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server-nar/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock-server-nar</artifactId>
+ <packaging>nar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-server</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ </dependencies>
+
+ <repositories>
+ <!-- Shibboleth Repository required for OpenSAML -->
+ <!-- This is required because nifi-framework-nar is the parent/NAR Dependency, andi t requires shibboleth. -->
+ <repository>
+ <id>shibboleth</id>
+ <url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+</project>
\ No newline at end of file
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml
new file mode 100644
index 0000000000..040596e620
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock-server</artifactId>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-core-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-core</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-encryptor</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-server-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+
+
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockAuditService.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockAuditService.java
new file mode 100644
index 0000000000..3aa7bbc543
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockAuditService.java
@@ -0,0 +1,63 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.action.Action;
+import org.apache.nifi.admin.service.AuditService;
+import org.apache.nifi.history.History;
+import org.apache.nifi.history.HistoryQuery;
+import org.apache.nifi.history.PreviousValue;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+public class MockAuditService implements AuditService {
+ @Override
+ public void addActions(final Collection<Action> actions) {
+ }
+
+ @Override
+ public Map<String, List<PreviousValue>> getPreviousValues(final String componentId) {
+ return Map.of();
+ }
+
+ @Override
+ public void deletePreviousValues(final String propertyName, final String componentId) {
+ }
+
+ @Override
+ public History getActions(final HistoryQuery actionQuery) {
+ return null;
+ }
+
+ @Override
+ public History getActions(final int firstActionId, final int maxActions) {
+ return null;
+ }
+
+ @Override
+ public Action getAction(final Integer actionId) {
+ return null;
+ }
+
+ @Override
+ public void purgeActions(final Date end, final Action purgeAction) {
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
new file mode 100644
index 0000000000..38dcec3474
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorInitializationContext.java
@@ -0,0 +1,156 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.components.connector.FrameworkConnectorInitializationContextBuilder;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.FrameworkConnectorInitializationContext;
+import org.apache.nifi.components.connector.FrameworkFlowContext;
+import org.apache.nifi.components.connector.SecretsManager;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.flow.VersionedProcessor;
+import org.apache.nifi.logging.ComponentLog;
+
+public class MockConnectorInitializationContext implements FrameworkConnectorInitializationContext {
+
+ private final String identifier;
+ private final String name;
+ private final ComponentLog componentLog;
+ private final SecretsManager secretsManager;
+ private final AssetManager assetManager;
+
+ private final MockExtensionMapper mockExtensionMapper;
+
+ private MockConnectorInitializationContext(final Builder builder) {
+ this.identifier = builder.identifier;
+ this.name = builder.name;
+ this.componentLog = builder.componentLog;
+ this.secretsManager = builder.secretsManager;
+ this.assetManager = builder.assetManager;
+ this.mockExtensionMapper = builder.mockExtensionMapper;
+ }
+
+
+ @Override
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public ComponentLog getLogger() {
+ return componentLog;
+ }
+
+ @Override
+ public SecretsManager getSecretsManager() {
+ return secretsManager;
+ }
+
+ @Override
+ public AssetManager getAssetManager() {
+ return assetManager;
+ }
+
+ @Override
+ public void updateFlow(final FlowContext flowContext, final VersionedExternalFlow versionedExternalFlow) throws FlowUpdateException {
+ if (!(flowContext instanceof final FrameworkFlowContext frameworkFlowContext)) {
+ throw new IllegalArgumentException("FlowContext is not an instance provided by the framework");
+ }
+
+ replaceMocks(versionedExternalFlow.getFlowContents());
+
+ // TODO: Probably should eliminate this method and instead move AssetManager to the FlowContext and add a method there
+ // to update the flow.
+ frameworkFlowContext.updateFlow(versionedExternalFlow, assetManager);
+ }
+
+ private void replaceMocks(final VersionedProcessGroup group) {
+ if (group.getProcessors() != null) {
+ for (final VersionedProcessor processor : group.getProcessors()) {
+ mockExtensionMapper.mapProcessor(processor);
+ }
+ }
+
+ if (group.getProcessGroups() != null) {
+ for (final VersionedProcessGroup childGroup : group.getProcessGroups()) {
+ replaceMocks(childGroup);
+ }
+ }
+ }
+
+ private void updateParameterContext(final VersionedProcessGroup group, final String parameterContextName) {
+ group.setParameterContextName(parameterContextName);
+ if (group.getProcessGroups() != null) {
+ for (final VersionedProcessGroup childGroup : group.getProcessGroups()) {
+ updateParameterContext(childGroup, parameterContextName);
+ }
+ }
+ }
+
+
+ public static class Builder implements FrameworkConnectorInitializationContextBuilder {
+ private final MockExtensionMapper mockExtensionMapper;
+ private String identifier;
+ private String name;
+ private ComponentLog componentLog;
+ private SecretsManager secretsManager;
+ private AssetManager assetManager;
+
+ public Builder(final MockExtensionMapper mockExtensionMapper) {
+ this.mockExtensionMapper = mockExtensionMapper;
+ }
+
+ public Builder identifier(final String identifier) {
+ this.identifier = identifier;
+ return this;
+ }
+
+ public Builder name(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder componentLog(final ComponentLog componentLog) {
+ this.componentLog = componentLog;
+ return this;
+ }
+
+ public Builder secretsManager(final SecretsManager secretsManager) {
+ this.secretsManager = secretsManager;
+ return this;
+ }
+
+ public Builder assetManager(final AssetManager assetManager) {
+ this.assetManager = assetManager;
+ return this;
+ }
+
+ public MockConnectorInitializationContext build() {
+ return new MockConnectorInitializationContext(this);
+ }
+
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorRepository.java
similarity index 50%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
copy to nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorRepository.java
index 96806fca1d..9f95111674 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockConnectorRepository.java
@@ -14,23 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.controller;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.ValidationContext;
-import org.apache.nifi.controller.flowanalysis.FlowAnalyzer;
-import org.apache.nifi.parameter.ParameterContext;
-import org.apache.nifi.validation.RuleViolationsManager;
+package org.apache.nifi.mock.connector.server;
-import java.util.Map;
-import java.util.Optional;
+import org.apache.nifi.components.connector.FrameworkConnectorInitializationContextBuilder;
+import org.apache.nifi.components.connector.StandardConnectorRepository;
-public interface ValidationContextFactory {
+public class MockConnectorRepository extends StandardConnectorRepository {
- ValidationContext newValidationContext(Map<PropertyDescriptor, PropertyConfiguration> properties, String annotationData, String groupId, String componentId, ParameterContext parameterContext,
- boolean validateConnections);
+ private volatile MockExtensionMapper mockExtensionMapper;
- Optional<RuleViolationsManager> getRuleViolationsManager();
+ public void setMockExtensionMapper(final MockExtensionMapper mapper) {
+ mockExtensionMapper = mapper;
+ }
- Optional<FlowAnalyzer> getFlowAnalyzer();
+ @Override
+ public FrameworkConnectorInitializationContextBuilder createInitializationContextBuilder() {
+ return new MockConnectorInitializationContext.Builder(mockExtensionMapper);
+ }
}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
new file mode 100644
index 0000000000..7af2240d2d
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
@@ -0,0 +1,60 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.nar.InstanceClassLoader;
+import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
+import org.apache.nifi.processor.Processor;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class MockExtensionDiscoveringManager extends StandardExtensionDiscoveringManager {
+ private final ConcurrentMap<String, ClassLoader> mockComponentClassLoaders = new ConcurrentHashMap<>();
+
+ public synchronized void addProcessor(final Class<? extends Processor> mockProcessorClass) {
+ final BundleDetails bundleDetails = new BundleDetails.Builder()
+ .workingDir(new File("target/work/extensions/mock-bundle"))
+ .coordinate(new BundleCoordinate("org.apache.nifi.mock", mockProcessorClass.getName(), "1.0.0"))
+ .build();
+
+ final Bundle mockBundle = new Bundle(bundleDetails, mockProcessorClass.getClassLoader());
+ discoverExtensions(Set.of(mockBundle));
+
+ mockComponentClassLoaders.put(mockProcessorClass.getName(), mockProcessorClass.getClassLoader());
+ registerExtensionClass(Processor.class, mockProcessorClass.getName(), mockBundle);
+ }
+
+ @Override
+ public InstanceClassLoader createInstanceClassLoader(final String classType, final String instanceIdentifier, final Bundle bundle, final Set<URL> additionalUrls,
+ final boolean register, final String classloaderIsolationKey) {
+
+ final ClassLoader classLoader = mockComponentClassLoaders.get(classType);
+ if (classLoader != null) {
+ return new InstanceClassLoader(instanceIdentifier, classType, additionalUrls, Set.of(), classLoader);
+ }
+
+ return super.createInstanceClassLoader(classType, instanceIdentifier, bundle, additionalUrls, register, classloaderIsolationKey);
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionMapper.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionMapper.java
new file mode 100644
index 0000000000..ed532cc253
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionMapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.flow.Bundle;
+import org.apache.nifi.flow.VersionedProcessor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MockExtensionMapper {
+
+ private final Map<String, String> processorMocks = new HashMap<>();
+
+ public void mockProcessor(final String processorType, final String mockProcessorClassName) {
+ processorMocks.put(processorType, mockProcessorClassName);
+ }
+
+ public void mapProcessor(final VersionedProcessor processor) {
+ final String type = processor.getType();
+ final String implementationClassName = processorMocks.get(type);
+ if (implementationClassName == null) {
+ return;
+ }
+
+ processor.setType(implementationClassName);
+ processor.setBundle(new Bundle("org.apache.nifi.mock", implementationClassName, "1.0.0"));
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockRuleViolationsManager.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockRuleViolationsManager.java
new file mode 100644
index 0000000000..4c3d5fd8c5
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockRuleViolationsManager.java
@@ -0,0 +1,79 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.flow.VersionedComponent;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.validation.RuleViolation;
+import org.apache.nifi.validation.RuleViolationsManager;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class MockRuleViolationsManager implements RuleViolationsManager {
+ @Override
+ public void upsertComponentViolations(final String subjectId, final Collection<RuleViolation> violations) {
+
+ }
+
+ @Override
+ public void upsertGroupViolations(final VersionedProcessGroup processGroup, final Collection<RuleViolation> violations, final Map<VersionedComponent, Collection<RuleViolation>> componentToRuleViolations) {
+
+ }
+
+ @Override
+ public Collection<RuleViolation> getRuleViolationsForSubject(final String subjectId) {
+ return List.of();
+ }
+
+ @Override
+ public Collection<RuleViolation> getRuleViolationsForGroup(final String groupId) {
+ return List.of();
+ }
+
+ @Override
+ public Collection<RuleViolation> getRuleViolationsForGroups(final Collection<String> groupIds) {
+ return List.of();
+ }
+
+ @Override
+ public Collection<RuleViolation> getAllRuleViolations() {
+ return List.of();
+ }
+
+ @Override
+ public void removeRuleViolationsForSubject(final String subjectId) {
+
+ }
+
+ @Override
+ public void removeRuleViolationsForRule(final String ruleId) {
+
+ }
+
+ @Override
+ public void cleanUp() {
+
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockServerConfigVerificationResult.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockServerConfigVerificationResult.java
new file mode 100644
index 0000000000..fed82f9290
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockServerConfigVerificationResult.java
@@ -0,0 +1,52 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+
+import java.util.Collections;
+import java.util.List;
+
+public class MockServerConfigVerificationResult implements ConnectorConfigVerificationResult {
+ private final List<ConfigVerificationResult> results;
+
+ public MockServerConfigVerificationResult(final List<ConfigVerificationResult> results) {
+ this.results = results;
+ }
+
+ @Override
+ public List<ConfigVerificationResult> getAllResults() {
+ return Collections.unmodifiableList(results);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> getFailedResults() {
+ return results.stream()
+ .filter(result -> result.getOutcome() == Outcome.FAILED)
+ .toList();
+ }
+
+ @Override
+ public void assertNoFailures() {
+ final List<ConfigVerificationResult> failedResults = getFailedResults();
+ if (!failedResults.isEmpty()) {
+ throw new AssertionError("Configuration verification failed: " + failedResults);
+ }
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateManager.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateManager.java
new file mode 100644
index 0000000000..f22269ceab
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateManager.java
@@ -0,0 +1,89 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateMap;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MockStateManager implements StateManager {
+ private final AtomicLong versionCounter = new AtomicLong(0L);
+ private final boolean dropStateKeySupported;
+
+ private volatile StateMap localStateMap = new MockStateMap(null, -1L);
+ private volatile StateMap clusterStateMap = new MockStateMap(null, -1L);
+
+ public MockStateManager(final boolean dropStateKeySupported) {
+ this.dropStateKeySupported = dropStateKeySupported;
+ }
+
+ @Override
+ public synchronized void setState(final Map<String, String> state, final Scope scope) throws IOException {
+ final StateMap newStateMap = new MockStateMap(state, versionCounter.incrementAndGet());
+
+ if (scope == Scope.CLUSTER) {
+ clusterStateMap = newStateMap;
+ } else {
+ localStateMap = newStateMap;
+ }
+ }
+
+ @Override
+ public synchronized StateMap getState(final Scope scope) throws IOException {
+ if (scope == Scope.CLUSTER) {
+ return clusterStateMap;
+ } else {
+ return localStateMap;
+ }
+ }
+
+ @Override
+ public synchronized boolean replace(final StateMap oldValue, final Map<String, String> newValue, final Scope scope) throws IOException {
+ final StateMap currentStateMap = scope == Scope.CLUSTER ? clusterStateMap : localStateMap;
+
+ if (currentStateMap == oldValue) {
+ final StateMap newStateMap = new MockStateMap(newValue, versionCounter.incrementAndGet());
+
+ if (scope == Scope.CLUSTER) {
+ clusterStateMap = newStateMap;
+ } else {
+ localStateMap = newStateMap;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public synchronized void clear(final Scope scope) throws IOException {
+ setState(Collections.emptyMap(), scope);
+ }
+
+ @Override
+ public boolean isStateKeyDropSupported() {
+ return dropStateKeySupported;
+ }
+}
+
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateManagerProvider.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateManagerProvider.java
new file mode 100644
index 0000000000..77484904f2
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateManagerProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateManagerProvider;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class MockStateManagerProvider implements StateManagerProvider {
+ private final ConcurrentMap<String, StateManager> stateManagers = new ConcurrentHashMap<>();
+ private volatile boolean clusterProviderEnabled = false;
+
+ @Override
+ public StateManager getStateManager(final String componentId, final boolean dropStateKeySupported) {
+ return stateManagers.computeIfAbsent(componentId, id -> new MockStateManager(dropStateKeySupported));
+ }
+
+ @Override
+ public void onComponentRemoved(final String componentId) {
+ stateManagers.remove(componentId);
+ }
+
+ @Override
+ public void shutdown() {
+ stateManagers.clear();
+ }
+
+ @Override
+ public void enableClusterProvider() {
+ clusterProviderEnabled = true;
+ }
+
+ @Override
+ public void disableClusterProvider() {
+ clusterProviderEnabled = false;
+ }
+
+ @Override
+ public boolean isClusterProviderEnabled() {
+ return clusterProviderEnabled;
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateMap.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateMap.java
new file mode 100644
index 0000000000..54b0980d61
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockStateMap.java
@@ -0,0 +1,56 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.components.state.StateMap;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class MockStateMap implements StateMap {
+ private final Map<String, String> stateValues;
+ private final long version;
+
+ public MockStateMap(final Map<String, String> stateValues, final long version) {
+ this.stateValues = stateValues == null ? Collections.emptyMap() : new HashMap<>(stateValues);
+ this.version = version;
+ }
+
+ @Override
+ public Optional<String> getStateVersion() {
+ return version == -1L ? Optional.empty() : Optional.of(Long.toString(version));
+ }
+
+ @Override
+ public String get(final String key) {
+ return stateValues.get(key);
+ }
+
+ @Override
+ public Map<String, String> toMap() {
+ return Collections.unmodifiableMap(stateValues);
+ }
+
+ @Override
+ public String toString() {
+ return "MockStateMap[version=" + version + ", values=" + stateValues + "]";
+ }
+}
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/NopPropertyEncryptor.java
similarity index 50%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
copy to nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/NopPropertyEncryptor.java
index 96806fca1d..56fbe816b7 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/NopPropertyEncryptor.java
@@ -14,23 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.controller;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.ValidationContext;
-import org.apache.nifi.controller.flowanalysis.FlowAnalyzer;
-import org.apache.nifi.parameter.ParameterContext;
-import org.apache.nifi.validation.RuleViolationsManager;
+package org.apache.nifi.mock.connector.server;
-import java.util.Map;
-import java.util.Optional;
+import org.apache.nifi.encrypt.PropertyEncryptor;
-public interface ValidationContextFactory {
+public class NopPropertyEncryptor implements PropertyEncryptor {
+ @Override
+ public String encrypt(final String property) {
+ return property;
+ }
- ValidationContext newValidationContext(Map<PropertyDescriptor, PropertyConfiguration> properties, String annotationData, String groupId, String componentId, ParameterContext parameterContext,
- boolean validateConnections);
-
- Optional<RuleViolationsManager> getRuleViolationsManager();
-
- Optional<FlowAnalyzer> getFlowAnalyzer();
+ @Override
+ public String decrypt(final String encryptedProperty) {
+ return encryptedProperty;
+ }
}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/PermitAllAuthorizer.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/PermitAllAuthorizer.java
new file mode 100644
index 0000000000..75b2577f25
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/PermitAllAuthorizer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+
+public class PermitAllAuthorizer implements Authorizer {
+ @Override
+ public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException {
+ return AuthorizationResult.approved();
+ }
+
+ @Override
+ public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+ }
+
+ @Override
+ public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+ }
+
+ @Override
+ public void preDestruction() throws AuthorizerDestructionException {
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
new file mode 100644
index 0000000000..1b8034c9ff
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
@@ -0,0 +1,304 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.admin.service.AuditService;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.cluster.ClusterDetailsFactory;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.ConnectorNode;
+import org.apache.nifi.components.connector.ConnectorRepository;
+import org.apache.nifi.components.connector.ConnectorState;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.StandaloneConnectorRequestReplicator;
+import org.apache.nifi.components.state.StateManagerProvider;
+import org.apache.nifi.components.validation.DisabledServiceValidationResult;
+import org.apache.nifi.components.validation.ValidationState;
+import org.apache.nifi.connectable.FlowFileTransferCounts;
+import org.apache.nifi.controller.DecommissionTask;
+import org.apache.nifi.controller.FlowController;
+import org.apache.nifi.controller.repository.FlowFileEventRepository;
+import org.apache.nifi.controller.repository.metrics.RingBufferEventRepository;
+import org.apache.nifi.controller.status.history.StatusHistoryDumpFactory;
+import org.apache.nifi.controller.status.history.StatusHistoryRepository;
+import org.apache.nifi.controller.status.history.VolatileComponentStatusRepository;
+import org.apache.nifi.diagnostics.DiagnosticsFactory;
+import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.engine.FlowEngine;
+import org.apache.nifi.events.VolatileBulletinRepository;
+import org.apache.nifi.nar.ExtensionMapping;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.reporting.BulletinRepository;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.validation.RuleViolationsManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+public class StandardConnectorMockServer implements ConnectorMockServer {
+ private static final String CONNECTOR_ID = "test-connector";
+
+ private Bundle systemBundle;
+ private Set<Bundle> bundles;
+ private NiFiProperties nifiProperties;
+ private FlowController flowController;
+ private MockExtensionDiscoveringManager extensionManager;
+ private ConnectorNode connectorNode;
+ private FlowEngine flowEngine;
+ private MockExtensionMapper mockExtensionMapper;
+ private FlowFileTransferCounts initialFlowFileTransferCounts = new FlowFileTransferCounts(0L, 0L, 0L, 0L);
+
+ @Override
+ public void start() {
+ extensionManager = new MockExtensionDiscoveringManager();
+ extensionManager.discoverExtensions(systemBundle, bundles);
+ extensionManager.logClassLoaderMapping();
+
+ final FlowFileEventRepository flowFileEventRepository = new RingBufferEventRepository(5);
+ final Authorizer authorizer = new PermitAllAuthorizer();
+ final AuditService auditService = new MockAuditService();
+ final PropertyEncryptor propertyEncryptor = new NopPropertyEncryptor();
+ final BulletinRepository bulletinRepository = new VolatileBulletinRepository();
+ final StatusHistoryRepository statusHistoryRepository = new VolatileComponentStatusRepository(nifiProperties);
+ final RuleViolationsManager ruleViolationManager = new MockRuleViolationsManager();
+ final StateManagerProvider stateManagerProvider = new MockStateManagerProvider();
+
+ flowController = FlowController.createStandaloneInstance(
+ flowFileEventRepository,
+ null,
+ nifiProperties,
+ authorizer,
+ auditService,
+ propertyEncryptor,
+ bulletinRepository,
+ extensionManager,
+ statusHistoryRepository,
+ ruleViolationManager,
+ stateManagerProvider,
+ new StandaloneConnectorRequestReplicator());
+
+ try {
+ flowController.getRepositoryContextFactory().getFlowFileRepository().loadFlowFiles(Collections::emptyList);
+ } catch (final IOException e) {
+ throw new RuntimeException("Failed to initialize FlowFile Repository", e);
+ }
+
+ final ConnectorRepository connectorRepository = flowController.getConnectorRepository();
+ if (!(connectorRepository instanceof MockConnectorRepository)) {
+ throw new IllegalStateException("Connector Repository is not an instance of MockConnectorRepository");
+ }
+
+ mockExtensionMapper = new MockExtensionMapper();
+ ((MockConnectorRepository) connectorRepository).setMockExtensionMapper(mockExtensionMapper);
+
+ flowEngine = new FlowEngine(4, "Connector Threads");
+ }
+
+ @Override
+ public void initialize(final NiFiProperties properties, final Bundle systemBundle, final Set<Bundle> bundles, final ExtensionMapping extensionMapping) {
+ this.systemBundle = systemBundle;
+ this.bundles = bundles;
+ this.nifiProperties = properties;
+ }
+
+ @Override
+ public void stop() {
+ if (flowEngine != null) {
+ flowEngine.shutdown();
+ }
+ if (flowController != null) {
+ flowController.shutdown(false);
+ }
+ }
+
+ @Override
+ public DiagnosticsFactory getDiagnosticsFactory() {
+ return null;
+ }
+
+ @Override
+ public DiagnosticsFactory getThreadDumpFactory() {
+ return null;
+ }
+
+ @Override
+ public DecommissionTask getDecommissionTask() {
+ return null;
+ }
+
+ @Override
+ public ClusterDetailsFactory getClusterDetailsFactory() {
+ return null;
+ }
+
+ @Override
+ public StatusHistoryDumpFactory getStatusHistoryDumpFactory() {
+ return null;
+ }
+
+ @Override
+ public void instantiateConnector(final String connectorClassName) {
+ final List<Bundle> bundles = extensionManager.getBundles(connectorClassName);
+ if (bundles.isEmpty()) {
+ throw new IllegalStateException("No bundles found for connector class: " + connectorClassName + " - ensure that you have included all relevant NARs in the configured lib directory");
+ }
+ if (bundles.size() > 1) {
+ throw new IllegalStateException("Multiple bundles found for connector class: " + connectorClassName + " - unable to determine which bundle to use. Ensure that only a single version of " +
+ "the Connector is included in the configured lib directory. Available bundles: " + bundles);
+ }
+
+ final BundleCoordinate bundleCoordinate = bundles.getFirst().getBundleDetails().getCoordinate();
+ connectorNode = flowController.getFlowManager().createConnector(connectorClassName, CONNECTOR_ID, bundleCoordinate, true, true);
+ }
+
+ @Override
+ public void registerMockBundle(final ClassLoader classLoader, final File workingDirectory) {
+ final BundleDetails mockBundleDetails = new BundleDetails.Builder()
+ .workingDir(workingDirectory)
+ .coordinate(new BundleCoordinate("org.apache.nifi.mock", "nifi-connector-mock-bundle", "1.0.0"))
+ .build();
+ }
+
+ @Override
+ public void applyUpdate() throws FlowUpdateException {
+ final ConnectorState initialDesiredState = connectorNode.getDesiredState();
+
+ connectorNode.prepareForUpdate();
+ connectorNode.applyUpdate();
+
+ if (initialDesiredState == ConnectorState.RUNNING) {
+ connectorNode.start(flowEngine);
+ } else {
+ connectorNode.stop(flowEngine);
+ }
+ }
+
+ @Override
+ public void configure(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) throws FlowUpdateException {
+ connectorNode.setConfiguration(stepName, groupConfigurations);
+ }
+
+ @Override
+ public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) {
+ final List<ConfigVerificationResult> results = connectorNode.verifyConfigurationStep(stepName, groupConfigurations);
+ return new MockServerConfigVerificationResult(results);
+ }
+
+ @Override
+ public void startConnector() {
+ initialFlowFileTransferCounts = connectorNode.getFlowFileTransferCounts();
+
+ connectorNode.start(flowEngine);
+ }
+
+ @Override
+ public void stopConnector() {
+ try {
+ connectorNode.stop(flowEngine).get(10, TimeUnit.SECONDS);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while waiting for connector to stop", e);
+ } catch (final Exception e) {
+ throw new RuntimeException("Failed to stop Connector", e);
+ }
+ }
+
+ @Override
+ public void waitForDataIngested(final Duration maxWaitTime) {
+ final long startTime = System.currentTimeMillis();
+ final long expirationTime = startTime + maxWaitTime.toMillis();
+
+ while (getTransferCountsSinceStart().getReceivedCount() == 0L) {
+ if (System.currentTimeMillis() > expirationTime) {
+ throw new RuntimeException("Timed out waiting for data to be ingested by the Connector");
+ }
+
+ try {
+ Thread.sleep(100L);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while waiting for data to be ingested by the Connector", e);
+ }
+ }
+ }
+
+ private FlowFileTransferCounts getTransferCountsSinceStart() {
+ final FlowFileTransferCounts currentCounts = connectorNode.getFlowFileTransferCounts();
+ return currentCounts.minus(initialFlowFileTransferCounts);
+ }
+
+ @Override
+ public void waitForIdle(final Duration maxWaitTime) {
+ waitForIdle(Duration.ofMillis(0L), maxWaitTime);
+ }
+
+ @Override
+ public void waitForIdle(final Duration minimumIdleTime, final Duration maxWaitTime) {
+ Optional<Duration> idleTime = connectorNode.getIdleDuration();
+
+ // Wait until idleTime is not empty and is at least equal to minimumIdleTime
+ final long startTime = System.currentTimeMillis();
+ final long expirationTime = startTime + maxWaitTime.toMillis();
+
+ while (idleTime.isEmpty() || idleTime.get().compareTo(minimumIdleTime) <= 0) {
+ if (System.currentTimeMillis() > expirationTime) {
+ throw new RuntimeException("Timed out waiting for Connector to be idle");
+ }
+
+ try {
+ Thread.sleep(100L);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Interrupted while waiting for Connector to be idle", e);
+ }
+
+ idleTime = connectorNode.getIdleDuration();
+ }
+ }
+
+ @Override
+ public List<ValidationResult> validate() {
+ final ValidationState validationState = connectorNode.performValidation();
+ return validationState.getValidationErrors().stream()
+ .filter(result -> !result.isValid())
+ .filter(result -> !DisabledServiceValidationResult.isMatch(result))
+ .toList();
+ }
+
+ @Override
+ public void mockProcessor(final String processorType, final Class<? extends Processor> mockProcessorClass) {
+ mockExtensionMapper.mockProcessor(processorType, mockProcessorClass.getName());
+ extensionManager.addProcessor(mockProcessorClass);
+ }
+
+ @Override
+ public void close() {
+ stop();
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.NiFiServer
similarity index 80%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
copy to nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.NiFiServer
index 56740ff7c3..70572a99ea 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.NiFiServer
@@ -12,6 +12,5 @@
# 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.
-org.apache.nifi.test.processors.ModifiesClasspathProcessor
-org.apache.nifi.test.processors.ModifiesClasspathNoAnnotationProcessor
-org.apache.nifi.controller.service.mock.DummyProcessor
\ No newline at end of file
+
+org.apache.nifi.mock.connector.server.StandardConnectorMockServer
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.components.connector.ConnectorRepository
similarity index 80%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
copy to nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.components.connector.ConnectorRepository
index 56740ff7c3..d9230afaec 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/resources/META-INF/services/org.apache.nifi.components.connector.ConnectorRepository
@@ -12,6 +12,5 @@
# 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.
-org.apache.nifi.test.processors.ModifiesClasspathProcessor
-org.apache.nifi.test.processors.ModifiesClasspathNoAnnotationProcessor
-org.apache.nifi.controller.service.mock.DummyProcessor
\ No newline at end of file
+
+org.apache.nifi.mock.connector.server.MockConnectorRepository
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockStateManagerProviderTest.java b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockStateManagerProviderTest.java
new file mode 100644
index 0000000000..d8ecf8e872
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/MockStateManagerProviderTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.mock.connector.server;
+
+import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.components.state.StateMap;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class MockStateManagerProviderTest {
+
+ @Test
+ void testGetStateManagerReturnsSameInstance() {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager1 = provider.getStateManager("component1", false);
+ final StateManager stateManager2 = provider.getStateManager("component1", false);
+
+ assertNotNull(stateManager1);
+ assertSame(stateManager1, stateManager2);
+ }
+
+ @Test
+ void testGetStateManagerReturnsDifferentInstancesForDifferentComponents() {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager1 = provider.getStateManager("component1", false);
+ final StateManager stateManager2 = provider.getStateManager("component2", false);
+
+ assertNotNull(stateManager1);
+ assertNotNull(stateManager2);
+ assertNotSame(stateManager1, stateManager2);
+ }
+
+ @Test
+ void testOnComponentRemoved() {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager1 = provider.getStateManager("component1", false);
+ assertNotNull(stateManager1);
+
+ provider.onComponentRemoved("component1");
+
+ final StateManager stateManager2 = provider.getStateManager("component1", false);
+ assertNotNull(stateManager2);
+ assertNotSame(stateManager1, stateManager2);
+ }
+
+ @Test
+ void testShutdown() {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ provider.getStateManager("component1", false);
+ provider.getStateManager("component2", false);
+
+ provider.shutdown();
+
+ final StateManager stateManager = provider.getStateManager("component1", false);
+ assertNotNull(stateManager);
+ }
+
+ @Test
+ void testClusterProviderEnabled() {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ assertFalse(provider.isClusterProviderEnabled());
+
+ provider.enableClusterProvider();
+ assertTrue(provider.isClusterProviderEnabled());
+
+ provider.disableClusterProvider();
+ assertFalse(provider.isClusterProviderEnabled());
+ }
+
+ @Test
+ void testStateManagerSetAndGetLocalState() throws IOException {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager = provider.getStateManager("component1", false);
+
+ final Map<String, String> state = new HashMap<>();
+ state.put("key1", "value1");
+ state.put("key2", "value2");
+
+ stateManager.setState(state, Scope.LOCAL);
+
+ final StateMap stateMap = stateManager.getState(Scope.LOCAL);
+ assertNotNull(stateMap);
+ assertTrue(stateMap.getStateVersion().isPresent());
+ assertEquals("value1", stateMap.get("key1"));
+ assertEquals("value2", stateMap.get("key2"));
+ assertEquals(2, stateMap.toMap().size());
+ }
+
+ @Test
+ void testStateManagerSetAndGetClusterState() throws IOException {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager = provider.getStateManager("component1", false);
+
+ final Map<String, String> state = new HashMap<>();
+ state.put("key1", "value1");
+ state.put("key2", "value2");
+
+ stateManager.setState(state, Scope.CLUSTER);
+
+ final StateMap stateMap = stateManager.getState(Scope.CLUSTER);
+ assertNotNull(stateMap);
+ assertTrue(stateMap.getStateVersion().isPresent());
+ assertEquals("value1", stateMap.get("key1"));
+ assertEquals("value2", stateMap.get("key2"));
+ }
+
+ @Test
+ void testStateManagerReplace() throws IOException {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager = provider.getStateManager("component1", false);
+
+ final Map<String, String> initialState = new HashMap<>();
+ initialState.put("key1", "value1");
+ stateManager.setState(initialState, Scope.LOCAL);
+
+ final StateMap oldStateMap = stateManager.getState(Scope.LOCAL);
+
+ final Map<String, String> newState = new HashMap<>();
+ newState.put("key1", "value2");
+
+ final boolean replaced = stateManager.replace(oldStateMap, newState, Scope.LOCAL);
+ assertTrue(replaced);
+
+ final StateMap updatedStateMap = stateManager.getState(Scope.LOCAL);
+ assertEquals("value2", updatedStateMap.get("key1"));
+
+ final boolean replacedAgain = stateManager.replace(oldStateMap, initialState, Scope.LOCAL);
+ assertFalse(replacedAgain);
+ }
+
+ @Test
+ void testStateManagerClear() throws IOException {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager = provider.getStateManager("component1", false);
+
+ final Map<String, String> state = new HashMap<>();
+ state.put("key1", "value1");
+ stateManager.setState(state, Scope.LOCAL);
+
+ stateManager.clear(Scope.LOCAL);
+
+ final StateMap stateMap = stateManager.getState(Scope.LOCAL);
+ assertNotNull(stateMap);
+ assertTrue(stateMap.toMap().isEmpty());
+ }
+
+ @Test
+ void testStateKeyDropSupported() {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager1 = provider.getStateManager("component1", false);
+ assertFalse(stateManager1.isStateKeyDropSupported());
+
+ final StateManager stateManager2 = provider.getStateManager("component2", true);
+ assertTrue(stateManager2.isStateKeyDropSupported());
+ }
+
+ @Test
+ void testThreadSafety() throws InterruptedException, IOException {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager = provider.getStateManager("component1", false);
+ final int threadCount = 10;
+ final int operationsPerThread = 100;
+ final ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ final CountDownLatch completionLatch = new CountDownLatch(threadCount);
+ final AtomicInteger successCount = new AtomicInteger(0);
+
+ for (int i = 0; i < threadCount; i++) {
+ final int threadId = i;
+ executorService.submit(() -> {
+ try {
+ startLatch.await();
+ for (int j = 0; j < operationsPerThread; j++) {
+ final Map<String, String> state = new HashMap<>();
+ state.put("thread", String.valueOf(threadId));
+ state.put("operation", String.valueOf(j));
+ stateManager.setState(state, Scope.LOCAL);
+
+ final StateMap stateMap = stateManager.getState(Scope.LOCAL);
+ if (stateMap != null && stateMap.toMap().size() == 2) {
+ successCount.incrementAndGet();
+ }
+ }
+ } catch (final Exception e) {
+ e.printStackTrace();
+ } finally {
+ completionLatch.countDown();
+ }
+ });
+ }
+
+ startLatch.countDown();
+ completionLatch.await(30, TimeUnit.SECONDS);
+ executorService.shutdown();
+
+ assertEquals(threadCount * operationsPerThread, successCount.get());
+ final StateMap finalState = stateManager.getState(Scope.LOCAL);
+ assertNotNull(finalState);
+ assertEquals(2, finalState.toMap().size());
+ }
+
+ @Test
+ void testEmptyStateMap() throws IOException {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager = provider.getStateManager("component1", false);
+
+ final StateMap stateMap = stateManager.getState(Scope.LOCAL);
+ assertNotNull(stateMap);
+ assertFalse(stateMap.getStateVersion().isPresent());
+ assertTrue(stateMap.toMap().isEmpty());
+ }
+
+ @Test
+ void testVersionIncrementing() throws IOException {
+ final MockStateManagerProvider provider = new MockStateManagerProvider();
+ final StateManager stateManager = provider.getStateManager("component1", false);
+
+ final Map<String, String> state1 = new HashMap<>();
+ state1.put("key", "value1");
+ stateManager.setState(state1, Scope.LOCAL);
+ final StateMap stateMap1 = stateManager.getState(Scope.LOCAL);
+
+ final Map<String, String> state2 = new HashMap<>();
+ state2.put("key", "value2");
+ stateManager.setState(state2, Scope.LOCAL);
+ final StateMap stateMap2 = stateManager.getState(Scope.LOCAL);
+
+ assertTrue(stateMap1.getStateVersion().isPresent());
+ assertTrue(stateMap2.getStateVersion().isPresent());
+
+ final long version1 = Long.parseLong(stateMap1.getStateVersion().get());
+ final long version2 = Long.parseLong(stateMap2.getStateVersion().get());
+ assertTrue(version2 > version1);
+ }
+}
+
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
new file mode 100644
index 0000000000..959ac55052
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-test-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock-integration-tests</artifactId>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- NARs required for integration testing -->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-server-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-test-connectors-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+
+ <!-- NARs for components referenced in the Connectors -->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-standard-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-standard-services-api-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-standard-shared-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-update-attribute-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-nar</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/libDir</outputDirectory>
+ <includeTypes>jar,nar</includeTypes>
+ <includeScope>compile</includeScope>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
similarity index 50%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
copy to nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
index 96806fca1d..dccbaaa705 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
@@ -14,23 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.nifi.controller;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.ValidationContext;
-import org.apache.nifi.controller.flowanalysis.FlowAnalyzer;
-import org.apache.nifi.parameter.ParameterContext;
-import org.apache.nifi.validation.RuleViolationsManager;
+package org.apache.nifi.mock.connectors.tests;
-import java.util.Map;
-import java.util.Optional;
+import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
+import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
+import org.junit.jupiter.api.Test;
-public interface ValidationContextFactory {
+import java.io.File;
+import java.io.IOException;
- ValidationContext newValidationContext(Map<PropertyDescriptor, PropertyConfiguration> properties, String annotationData, String groupId, String componentId, ParameterContext parameterContext,
- boolean validateConnections);
+public class CreateConnectorIT {
- Optional<RuleViolationsManager> getRuleViolationsManager();
+ @Test
+ public void testCreateStartAndStopGenerateAndUpdateConnector() throws IOException {
+ try (final ConnectorTestRunner testRunner = new StandardConnectorTestRunner.Builder()
+ .connectorClassName("org.apache.nifi.mock.connectors.GenerateAndLog")
+ .narLibraryDirectory(new File("target/libDir"))
+ .build()) {
- Optional<FlowAnalyzer> getFlowAnalyzer();
+ testRunner.startConnector();
+ testRunner.stopConnector();
+ }
+ }
}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockProcessorIT.java b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockProcessorIT.java
new file mode 100644
index 0000000000..465d034000
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockProcessorIT.java
@@ -0,0 +1,91 @@
+/*
+ * 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.mock.connectors.tests;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.Validator;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
+import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MockProcessorIT {
+
+ @Test
+ @Timeout(10)
+ public void testMockProcessor() throws IOException {
+ try (final ConnectorTestRunner runner = new StandardConnectorTestRunner.Builder()
+ .narLibraryDirectory(new File("target/libDir"))
+ .connectorClassName("org.apache.nifi.mock.connectors.GenerateAndLog")
+ .mockProcessor("org.apache.nifi.processors.attributes.UpdateAttribute", MockProcessor.class)
+ .build()) {
+
+ runner.startConnector();
+
+ // Wait until MockProcessor is triggered at least once. We use @Timeout on the test to avoid
+ // hanging indefinitely in case of failure.
+ while (MockProcessor.invocationCounter.get() < 1) {
+ Thread.yield();
+ }
+ runner.stopConnector();
+ }
+ }
+
+ public static class MockProcessor extends AbstractProcessor {
+ private static final AtomicLong invocationCounter = new AtomicLong(0L);
+
+ public static final Relationship REL_SUCCESS = new Relationship.Builder()
+ .name("success")
+ .build();
+
+ @Override
+ public Set<Relationship> getRelationships() {
+ return Set.of(REL_SUCCESS);
+ }
+
+ // Support any properties so that properties of UpdateAttribute can be set without the Processor becoming invalid
+ @Override
+ protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
+ return new PropertyDescriptor.Builder()
+ .name(propertyDescriptorName)
+ .addValidator(Validator.VALID)
+ .build();
+ }
+
+ @Override
+ public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
+ invocationCounter.incrementAndGet();
+
+ final FlowFile flowFile = session.get();
+ if (flowFile != null) {
+ session.transfer(flowFile, REL_SUCCESS);
+ }
+ }
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors-nar/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors-nar/pom.xml
new file mode 100644
index 0000000000..89d3a10121
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors-nar/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-test-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock-test-connectors-nar</artifactId>
+ <packaging>nar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-test-connectors</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
+
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/pom.xml
new file mode 100644
index 0000000000..9f2a86cd96
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-test-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock-test-connectors</artifactId>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-utils</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+
+
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
+
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
new file mode 100644
index 0000000000..f9775bc2d8
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/java/org/apache/nifi/mock/connectors/GenerateAndLog.java
@@ -0,0 +1,56 @@
+/*
+ * 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.mock.connectors;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.connector.AbstractConnector;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.components.connector.util.VersionedFlowUtils;
+import org.apache.nifi.flow.VersionedExternalFlow;
+
+import java.util.List;
+import java.util.Map;
+
+public class GenerateAndLog extends AbstractConnector {
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ return VersionedFlowUtils.loadFlowFromResource("flows/Generate_and_Update.json");
+ }
+
+ @Override
+ protected void onStepConfigured(final String stepName, final FlowContext flowContext) {
+ }
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ return List.of();
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> overrides, final FlowContext workingContext) {
+ return List.of();
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
similarity index 80%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
copy to nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
index 56740ff7c3..c6cbc30195 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -12,6 +12,5 @@
# 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.
-org.apache.nifi.test.processors.ModifiesClasspathProcessor
-org.apache.nifi.test.processors.ModifiesClasspathNoAnnotationProcessor
-org.apache.nifi.controller.service.mock.DummyProcessor
\ No newline at end of file
+
+org.apache.nifi.mock.connectors.GenerateAndLog
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/flows/Generate_and_Update.json b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/flows/Generate_and_Update.json
new file mode 100644
index 0000000000..686247011e
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-test-connectors/src/main/resources/flows/Generate_and_Update.json
@@ -0,0 +1 @@
+{"flowContents":{"identifier":"cdf7f935-0045-3c88-bdad-fd4e34105756","instanceIdentifier":"3a87b58f-019a-1000-30d2-87dfdd9816aa","name":"Generate and Update","comments":"","position":{"x":-1124.0,"y":65.5},"processGroups":[],"remoteProcessGroups":[],"processors":[{"identifier":"4d4160b1-736c-3be4-bc3a-84fc6eb4ad2a","instanceIdentifier":"3a87db5f-019a-1000-75df-90e055f21fc9","name":"UpdateAttribute","comments":"","position":{"x":-358.0,"y":-106.5},"type":"org.apache.nifi.processors.attrib [...]
\ No newline at end of file
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/pom.xml
new file mode 100644
index 0000000000..c7b734eb41
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock-test-bundle</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>nifi-connector-mock-test-connectors</module>
+ <module>nifi-connector-mock-test-connectors-nar</module>
+ <module>nifi-connector-mock-integration-tests</module>
+ </modules>
+</project>
+
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/pom.xml b/nifi-connector-mock-bundle/nifi-connector-mock/pom.xml
new file mode 100644
index 0000000000..3f6ea8b8cd
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/pom.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock</artifactId>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-nar-utils</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-properties</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Dependencies necessary to bootstrap NiFi -->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-nar-utils</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+
+
+ <!-- Core NiFi API dependencies -->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-property-utils</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-python-framework-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-stateless-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Logging dependencies to ensure that everything funnels through SLF4J -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>log4j-over-slf4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jul-to-slf4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ </dependency>
+
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- NARs required -->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-jetty-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-provenance-repository-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-nar</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/libDir</outputDirectory>
+ <includeTypes>jar,nar</includeTypes>
+ <includeScope>compile</includeScope>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
new file mode 100644
index 0000000000..907204d0f0
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
@@ -0,0 +1,193 @@
+/*
+ * 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.mock.connector;
+
+import org.apache.nifi.NiFiServer;
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
+import org.apache.nifi.mock.connector.server.ConnectorMockServer;
+import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
+import org.apache.nifi.nar.ExtensionMapping;
+import org.apache.nifi.nar.NarClassLoaders;
+import org.apache.nifi.nar.NarUnpackMode;
+import org.apache.nifi.nar.NarUnpacker;
+import org.apache.nifi.nar.SystemBundle;
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.util.NiFiProperties;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class StandardConnectorTestRunner implements ConnectorTestRunner, Closeable {
+ private final File narLibraryDirectory;
+
+ private ConnectorMockServer mockServer;
+
+ private StandardConnectorTestRunner(final Builder builder) {
+ this.narLibraryDirectory = builder.narLibraryDirectory;
+
+ try {
+ bootstrapInstance();
+ } catch (final Exception e) {
+ throw new RuntimeException("Failed to bootstrap ConnectorTestRunner", e);
+ }
+
+ // It is important that we register the processor mocks before instantiating the connector.
+ // Otherwise, the call to instantiateConnector will initialize the Connector, which may update the flow.
+ // If the flow is updated before the processor mocks are registered, the Processors will be created without
+ // using the mocks. Subsequent updates to the flow will not replace the Processors already created because
+ // these are not recognized as updates to the flow, since the framework assumes that the type of a Processor
+ // with a given ID does not change.
+ builder.processorMocks.forEach(mockServer::mockProcessor);
+
+ mockServer.instantiateConnector(builder.connectorClassName);
+ }
+
+ private void bootstrapInstance() throws IOException, ClassNotFoundException {
+ final List<Path> libDirectoryPaths = List.of(narLibraryDirectory.toPath());
+ final File extensionsWorkingDir = new File("target/work/extensions");
+ final File frameworkWorkingDir = new File("target/work/framework");
+
+ final Bundle systemBundle = SystemBundle.create(narLibraryDirectory.getAbsolutePath(), ClassLoader.getSystemClassLoader());
+
+ final ExtensionMapping extensionMapping = NarUnpacker.unpackNars(systemBundle, frameworkWorkingDir, extensionsWorkingDir, libDirectoryPaths, true,
+ NarClassLoaders.FRAMEWORK_NAR_ID, true, false, NarUnpackMode.UNPACK_INDIVIDUAL_JARS, bundleCoordinate -> true);
+
+ final NarClassLoaders narClassLoaders = new NarClassLoaders();
+ narClassLoaders.init(frameworkWorkingDir, extensionsWorkingDir);
+
+ final NiFiServer nifiServer = narClassLoaders.getServer();
+ if (nifiServer == null) {
+ throw new RuntimeException("Could not find NiFiServer instance");
+ }
+ if (!(nifiServer instanceof ConnectorMockServer)) {
+ throw new RuntimeException("Test ClassPath does not contain ConnectorMockServer. " +
+ "Ensure that the appropriate module is packaged in the NAR library directory: " + narLibraryDirectory.getAbsolutePath());
+ }
+
+ // Set Application Server Class Loader for subsequent operations
+ final ClassLoader applicationServerClassLoader = narClassLoaders.getServer().getClass().getClassLoader();
+ Thread.currentThread().setContextClassLoader(applicationServerClassLoader);
+
+ final Set<Bundle> narBundles = narClassLoaders.getBundles();
+
+ final NiFiProperties properties;
+ try (final InputStream propertiesIn = getClass().getClassLoader().getResourceAsStream("nifi.properties")) {
+ properties = NiFiProperties.createBasicNiFiProperties(propertiesIn);
+ }
+
+ nifiServer.initialize(properties, systemBundle, narBundles, extensionMapping);
+ nifiServer.start();
+
+ mockServer = (ConnectorMockServer) nifiServer;
+ mockServer.registerMockBundle(getClass().getClassLoader(), new File(extensionsWorkingDir, "mock-implementations-bundle"));
+ }
+
+ @Override
+ public void close() {
+ if (mockServer != null) {
+ mockServer.stop();
+ }
+ }
+
+ @Override
+ public void applyUpdate() throws FlowUpdateException {
+ mockServer.applyUpdate();
+ }
+
+ @Override
+ public void configure(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) throws FlowUpdateException {
+ mockServer.configure(stepName, groupConfigurations);
+ }
+
+ @Override
+ public ConnectorConfigVerificationResult verifyConfiguration(final String stepName, final List<PropertyGroupConfiguration> groupConfigurations) {
+ return mockServer.verifyConfiguration(stepName, groupConfigurations);
+ }
+
+ @Override
+ public void startConnector() {
+ mockServer.startConnector();
+ }
+
+ @Override
+ public void stopConnector() {
+ mockServer.stopConnector();
+ }
+
+ @Override
+ public void waitForDataIngested(final Duration maxWaitTime) {
+ mockServer.waitForDataIngested(maxWaitTime);
+ }
+
+ @Override
+ public void waitForIdle(final Duration maxWaitTime) {
+ mockServer.waitForIdle(maxWaitTime);
+ }
+
+ @Override
+ public void waitForIdle(final Duration minIdleTime, final Duration maxWaitTime) {
+ mockServer.waitForIdle(minIdleTime, maxWaitTime);
+ }
+
+ @Override
+ public List<ValidationResult> validate() {
+ return mockServer.validate();
+ }
+
+
+ public static class Builder {
+ private String connectorClassName;
+ private File narLibraryDirectory;
+ private final Map<String, Class<? extends Processor>> processorMocks = new HashMap<>();
+
+ public Builder connectorClassName(final String connectorClassName) {
+ this.connectorClassName = connectorClassName;
+ return this;
+ }
+
+ public Builder narLibraryDirectory(final File libDirectory) {
+ this.narLibraryDirectory = libDirectory;
+ return this;
+ }
+
+ public Builder mockProcessor(final String processorType, final Class<? extends Processor> mockProcessorClass) {
+ processorMocks.put(processorType, mockProcessorClass);
+ return this;
+ }
+
+ public StandardConnectorTestRunner build() {
+ if (!narLibraryDirectory.exists() || !narLibraryDirectory.isDirectory()) {
+ throw new IllegalArgumentException("NAR file does not exist or is not a directory: " + narLibraryDirectory.getAbsolutePath());
+ }
+
+ return new StandardConnectorTestRunner(this);
+ }
+ }
+}
diff --git a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties
new file mode 100644
index 0000000000..046a2980b2
--- /dev/null
+++ b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/resources/nifi.properties
@@ -0,0 +1,159 @@
+# 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.
+
+# Core Properties #
+nifi.flow.configuration.file=target/nifi-storage/conf/flow.json.gz
+nifi.flow.configuration.archive.enabled=true
+nifi.flow.configuration.archive.dir=target/nifi-storage/conf/archive/
+nifi.flow.configuration.archive.max.time=30 days
+nifi.flow.configuration.archive.max.storage=500 MB
+nifi.flow.configuration.archive.max.count=
+nifi.flowcontroller.autoResumeState=true
+nifi.flowcontroller.graceful.shutdown.period=10 sec
+nifi.flowservice.writedelay.interval=500 ms
+nifi.administrative.yield.duration=3 sec
+# If a component has no work to do (is "bored"), how long should we wait before checking again for work?
+nifi.bored.yield.duration=10 millis
+nifi.queue.backpressure.count=10000
+nifi.queue.backpressure.size=1 GB
+
+nifi.nar.library.directory=./lib
+nifi.nar.library.autoload.directory=./extensions
+nifi.nar.working.directory=./work/nar/
+
+#####################
+# Python Extensions #
+#####################
+# Uncomment in order to enable Python Extensions.
+#nifi.python.command=python
+nifi.python.framework.source.directory=./python/framework
+nifi.python.extensions.source.directory.default=./python/extensions
+nifi.python.working.directory=./work/python
+nifi.python.max.processes=100
+nifi.python.max.processes.per.extension.type=10
+
+####################
+# State Management #
+####################
+nifi.state.management.configuration.file=target/nifi-storage/state-management.xml
+# The ID of the local state provider
+nifi.state.management.provider.local=local-provider
+
+# Database Settings
+nifi.database.directory=target/nifi-storage/database_repository
+
+# FlowFile Repository
+nifi.flowfile.repository.implementation=org.apache.nifi.controller.repository.WriteAheadFlowFileRepository
+nifi.flowfile.repository.wal.implementation=org.apache.nifi.wali.SequentialAccessWriteAheadLog
+nifi.flowfile.repository.directory=target/nifi-storage/flowfile_repository
+nifi.flowfile.repository.checkpoint.interval=1 mins
+nifi.flowfile.repository.always.sync=false
+
+nifi.swap.manager.implementation=org.apache.nifi.controller.FileSystemSwapManager
+nifi.queue.swap.threshold=20000
+nifi.swap.in.period=5 sec
+nifi.swap.in.threads=1
+nifi.swap.out.period=5 sec
+nifi.swap.out.threads=4
+
+# Content Repository
+nifi.content.repository.implementation=org.apache.nifi.controller.repository.FileSystemRepository
+nifi.content.claim.max.appendable.size=50 KB
+nifi.content.repository.directory.default=target/nifi-storage/content_repository
+nifi.content.repository.archive.max.retention.period=12 hours
+nifi.content.repository.archive.max.usage.percentage=90%
+nifi.content.repository.archive.enabled=false
+nifi.content.repository.always.sync=false
+
+# Provenance Repository Properties
+nifi.provenance.repository.implementation=org.apache.nifi.provenance.VolatileProvenanceRepository
+
+# Volatile Provenance Respository Properties
+nifi.provenance.repository.buffer.size=1000
+
+# Component Status Repository
+nifi.components.status.repository.implementation=org.apache.nifi.controller.status.history.VolatileComponentStatusRepository
+nifi.components.status.repository.buffer.size=1440
+nifi.components.status.snapshot.frequency=1 min
+
+# NAR Persistence Provider Properties
+nifi.nar.persistence.provider.properties.directory=target/nifi-storage/nar_repository
+
+# Asset Management
+nifi.asset.manager.properties.directory=target/nifi-storage/assets
+
+# Site to Site properties
+nifi.remote.input.host=localhost
+nifi.remote.input.secure=true
+nifi.remote.input.socket.port=7780
+nifi.remote.input.http.enabled=true
+nifi.remote.input.http.transaction.ttl=30 sec
+nifi.remote.contents.cache.expiration=30 secs
+
+# web properties #
+nifi.web.war.directory=./lib
+nifi.web.http.host=
+nifi.web.http.port=
+nifi.web.http.network.interface.default=
+nifi.web.https.host=localhost
+nifi.web.https.port=0
+nifi.web.https.network.interface.default=
+nifi.web.jetty.working.directory=target/work/jetty
+nifi.web.jetty.threads=200
+nifi.web.max.header.size=16 KB
+nifi.web.proxy.context.path=
+nifi.web.proxy.host=
+
+# security properties #
+nifi.sensitive.props.key=nifi-connector-mock-tests
+nifi.sensitive.props.algorithm=NIFI_PBKDF2_AES_GCM_256
+
+nifi.security.keystore=
+nifi.security.keystoreType=PKCS12
+nifi.security.keystorePasswd=
+nifi.security.keyPasswd=
+nifi.security.truststore=
+nifi.security.truststoreType=PKCS12
+nifi.security.truststorePasswd=
+nifi.security.user.authorizer=system-test-authorizer
+nifi.security.user.login.identity.provider=
+
+# cluster common properties (all nodes must have same values) #
+nifi.cluster.protocol.heartbeat.interval=5 sec
+nifi.cluster.protocol.is.secure=true
+
+# cluster node properties (only configure for cluster nodes) #
+nifi.cluster.is.node=false
+nifi.cluster.node.address=
+nifi.cluster.node.protocol.port=
+nifi.cluster.node.protocol.threads=10
+nifi.cluster.node.protocol.max.threads=50
+nifi.cluster.node.event.history.size=25
+nifi.cluster.node.connection.timeout=5 sec
+nifi.cluster.node.read.timeout=5 sec
+nifi.cluster.node.max.concurrent.requests=100
+nifi.cluster.firewall.file=
+nifi.cluster.flow.election.max.wait.time=5 mins
+nifi.cluster.flow.election.max.candidates=
+
+# cluster load balancing properties #
+nifi.cluster.load.balance.host=
+nifi.cluster.load.balance.port=6342
+nifi.cluster.load.balance.connections.per.node=4
+nifi.cluster.load.balance.max.thread.count=8
+nifi.cluster.load.balance.comms.timeout=30 sec
+
+# Connector Repository
+nifi.components.connectors.repository.implementation=org.apache.nifi.mock.connector.server.MockConnectorRepository
diff --git a/nifi-connector-mock-bundle/pom.xml b/nifi-connector-mock-bundle/pom.xml
new file mode 100644
index 0000000000..adb016336e
--- /dev/null
+++ b/nifi-connector-mock-bundle/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connector-mock-bundle</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>nifi-connector-mock</module>
+ <module>nifi-connector-mock-api</module>
+ <module>nifi-connector-mock-server</module>
+ <module>nifi-connector-mock-server-nar</module>
+ <module>nifi-connector-mock-test-bundle</module>
+ </modules>
+</project>
\ No newline at end of file
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/pom.xml
new file mode 100644
index 0000000000..7f95903475
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-to-s3-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-kafka-to-s3-connector</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-utils</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java
new file mode 100644
index 0000000000..793b1e9be0
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaConnectionStep.java
@@ -0,0 +1,135 @@
+/*
+ * 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.connectors.kafkas3;
+
+import org.apache.nifi.components.Validator;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.PropertyType;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.util.List;
+
+public class KafkaConnectionStep {
+ public static final String STEP_NAME = "Kafka Connection";
+
+ public static final ConnectorPropertyDescriptor KAFKA_BROKERS = new ConnectorPropertyDescriptor.Builder()
+ .name("Kafka Brokers")
+ .description("A comma-separated list of Kafka brokers to connect to.")
+ .required(true)
+ .validators(StandardValidators.HOSTNAME_PORT_LIST_VALIDATOR)
+ .type(PropertyType.STRING_LIST)
+ .build();
+
+ public static final ConnectorPropertyDescriptor SECURITY_PROTOCOL = new ConnectorPropertyDescriptor.Builder()
+ .name("Security Protocol")
+ .description("The security protocol to use when connecting to Kafka brokers.")
+ .required(true)
+ .type(PropertyType.STRING)
+ .defaultValue("SASL_PLAINTEXT")
+ .allowableValues("PLAINTEXT", "SSL", "SASL_PLAINTEXT", "SASL_SSL")
+ .build();
+
+ public static final ConnectorPropertyDescriptor SASL_MECHANISM = new ConnectorPropertyDescriptor.Builder()
+ .name("SASL Mechanism")
+ .description("The SASL mechanism to use for authentication.")
+ .required(true)
+ .type(PropertyType.STRING)
+ .allowableValues("PLAIN", "SCRAM-SHA-256", "SCRAM-SHA-512", "GSSAPI", "OAUTHBEARER")
+ .dependsOn(SECURITY_PROTOCOL, "SASL_PLAINTEXT", "SASL_SSL")
+ .defaultValue("SCRAM-SHA-512")
+ .build();
+
+ public static final ConnectorPropertyDescriptor USERNAME = new ConnectorPropertyDescriptor.Builder()
+ .name("Username")
+ .description("The username for SASL authentication.")
+ .required(true)
+ .type(PropertyType.STRING)
+ .validators(StandardValidators.NON_EMPTY_VALIDATOR)
+ .dependsOn(SECURITY_PROTOCOL, "SASL_PLAINTEXT", "SASL_SSL")
+ .build();
+
+ public static final ConnectorPropertyDescriptor PASSWORD = new ConnectorPropertyDescriptor.Builder()
+ .name("Password")
+ .description("The password for SASL authentication.")
+ .required(true)
+ .type(PropertyType.SECRET)
+ .validators(StandardValidators.NON_EMPTY_VALIDATOR)
+ .dependsOn(SECURITY_PROTOCOL, "SASL_PLAINTEXT", "SASL_SSL")
+ .build();
+
+ public static final ConnectorPropertyGroup KAFKA_SERVER_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Kafka Server Settings")
+ .description("Settings for connecting to the Kafka server")
+ .properties(List.of(
+ KAFKA_BROKERS,
+ SECURITY_PROTOCOL,
+ SASL_MECHANISM,
+ USERNAME,
+ PASSWORD
+ ))
+ .build();
+
+ public static final ConnectorPropertyDescriptor SCHEMA_REGISTRY_URL = new ConnectorPropertyDescriptor.Builder()
+ .name("Schema Registry URL")
+ .description("The URL of the Schema Registry.")
+ .required(false)
+ .type(PropertyType.STRING)
+ .validators(StandardValidators.URL_VALIDATOR)
+ .validators(Validator.VALID)
+ .build();
+
+ public static final ConnectorPropertyDescriptor SCHEMA_REGISTRY_USERNAME = new ConnectorPropertyDescriptor.Builder()
+ .name("Schema Registry Username")
+ .description("The username for Schema Registry authentication.")
+ .required(false)
+ .type(PropertyType.STRING)
+ .validators(StandardValidators.NON_EMPTY_VALIDATOR)
+ .dependsOn(SCHEMA_REGISTRY_URL)
+ .build();
+
+ public static final ConnectorPropertyDescriptor SCHEMA_REGISTRY_PASSWORD = new ConnectorPropertyDescriptor.Builder()
+ .name("Schema Registry Password")
+ .description("The password for Schema Registry authentication.")
+ .required(false)
+ .type(PropertyType.SECRET)
+ .validators(StandardValidators.NON_EMPTY_VALIDATOR)
+ .dependsOn(SCHEMA_REGISTRY_URL)
+ .build();
+
+
+ public static final ConnectorPropertyGroup SCHEMA_REGISTRY_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Schema Registry Settings")
+ .description("Settings for connecting to the Schema Registry")
+ .properties(List.of(
+ SCHEMA_REGISTRY_URL,
+ SCHEMA_REGISTRY_USERNAME,
+ SCHEMA_REGISTRY_PASSWORD
+ ))
+ .build();
+
+ public static final ConfigurationStep KAFKA_CONNECTION_STEP = new ConfigurationStep.Builder()
+ .name(STEP_NAME)
+ .description("Configure Kafka connection settings")
+ .propertyGroups(List.of(
+ KAFKA_SERVER_GROUP,
+ SCHEMA_REGISTRY_GROUP
+ ))
+ .build();
+}
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java
new file mode 100644
index 0000000000..c94226e132
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3.java
@@ -0,0 +1,295 @@
+/*
+ * 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.connectors.kafkas3;
+
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.components.connector.AbstractConnector;
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorConfigurationContext;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.InvocationFailedException;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.components.connector.components.ControllerServiceFacade;
+import org.apache.nifi.components.connector.components.FlowContext;
+import org.apache.nifi.components.connector.components.ProcessGroupFacade;
+import org.apache.nifi.components.connector.components.ProcessorFacade;
+import org.apache.nifi.components.connector.util.VersionedFlowUtils;
+import org.apache.nifi.flow.VersionedControllerService;
+import org.apache.nifi.flow.VersionedExternalFlow;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+@CapabilityDescription("Provides the ability to ingest data from Apache Kafka topics, merge it together into an object of reasonable " +
+ "size, and write that data to Amazon S3.")
+@Tags({"kafka", "s3"})
+public class KafkaToS3 extends AbstractConnector {
+
+ private volatile CompletableFuture<Void> drainFlowFileFuture = null;
+
+
+ @Override
+ public List<ConfigurationStep> getConfigurationSteps(final FlowContext flowContext) {
+ return List.of(
+ KafkaConnectionStep.KAFKA_CONNECTION_STEP,
+ KafkaTopicsStep.KAFKA_TOPICS_STEP,
+ S3Step.S3_STEP
+ );
+ }
+
+ @Override
+ public void prepareForUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
+ final String activeS3DataFormat = activeContext.getConfigurationContext().getProperty(
+ S3Step.S3_STEP_NAME, S3Step.S3_DESTINATION_GROUP.getName(), S3Step.S3_DATA_FORMAT.getName()).getValue();
+ final String workingS3DataFormat = workingContext.getConfigurationContext().getProperty(
+ S3Step.S3_STEP_NAME, S3Step.S3_DESTINATION_GROUP.getName(), S3Step.S3_DATA_FORMAT.getName()).getValue();
+
+ if (!activeS3DataFormat.equals(workingS3DataFormat)) {
+ getLogger().info("S3 Data Format changed from {} to {}; draining flow before updating it", activeS3DataFormat, workingS3DataFormat);
+
+ drainFlowFileFuture = drainFlowFiles(activeContext);
+ try {
+ drainFlowFileFuture.get(5, TimeUnit.MINUTES);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new FlowUpdateException("Interrupted while waiting for all FlowFiles to drain from the flow", e);
+ } catch (final TimeoutException e) {
+ throw new FlowUpdateException("Timed out waiting for all FlowFiles to drain from the flow", e);
+ } catch (final ExecutionException e) {
+ throw new FlowUpdateException("Failed to drain FlowFiles from flow", e.getCause());
+ }
+
+ getLogger().info("All FlowFiles drained from the flow; proceeding with flow update");
+ }
+ }
+
+ @Override
+ public void abortUpdate(final FlowContext workingContext, final Throwable throwable) {
+ if (drainFlowFileFuture != null) {
+ drainFlowFileFuture.cancel(true);
+ drainFlowFileFuture = null;
+ }
+ }
+
+ @Override
+ public void applyUpdate(final FlowContext workingContext, final FlowContext activeContext) throws FlowUpdateException {
+ final VersionedExternalFlow flow = buildFlow(workingContext.getConfigurationContext());
+ getInitializationContext().updateFlow(activeContext, flow);
+ }
+
+ @Override
+ public VersionedExternalFlow getInitialFlow() {
+ return KafkaToS3FlowBuilder.loadInitialFlow();
+ }
+
+ @Override
+ public void onStepConfigured(final String stepName, final FlowContext workingContext) throws FlowUpdateException {
+ final VersionedExternalFlow flow = buildFlow(workingContext.getConfigurationContext());
+ getInitializationContext().updateFlow(workingContext, flow);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfigurationStep(final String stepName, final List<PropertyGroupConfiguration> propertyOverrides, final FlowContext workingFlowContext) {
+ // Get the current ConfigurationContext and then create a new one that contains the provided property values
+ final ConnectorConfigurationContext configurationContext = workingFlowContext.getConfigurationContext().createWithOverrides(stepName, propertyOverrides);
+ final VersionedExternalFlow flow = buildFlow(configurationContext);
+
+ // Validate Connectivity
+ if (stepName.equals(KafkaConnectionStep.STEP_NAME)) {
+ return verifyKafkaConnectivity(workingFlowContext, flow);
+ }
+ if (stepName.equals(KafkaTopicsStep.STEP_NAME)) {
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+ results.addAll(verifyTopicsExists(workingFlowContext));
+ results.addAll(verifyKafkaParsability(workingFlowContext, flow));
+ return results;
+ }
+
+ return Collections.emptyList();
+ }
+
+ private List<ConfigVerificationResult> verifyKafkaParsability(final FlowContext workingFlowContext, final VersionedExternalFlow flow) {
+ // Enable Controller Services necessary for parsing records.
+ // We determine which Controller Services are referenced by the flow and enable them, but we do not use
+ // getRootGroup().getLifecycle().enableReferencedControllerServices(ControllerServiceReferenceScope.INCLUDE_REFERENCED_SERVICES_ONLY)
+ // because that would include the Controller Services that are referenced by the currently configured flow, and it's possible that the
+ // what is being verified uses a different set of Controller Services (e.g., the verified flow may use a JSON Reader while the current
+ // flow uses an Avro Reader).
+ final ProcessGroupFacade rootGroup = workingFlowContext.getRootGroup();
+ final Set<VersionedControllerService> referencedServices = VersionedFlowUtils.getReferencedControllerServices(flow.getFlowContents());
+ final Set<String> serviceIds = referencedServices.stream()
+ .map(VersionedControllerService::getIdentifier)
+ .collect(Collectors.toSet());
+
+ try {
+ rootGroup.getLifecycle().enableControllerServices(serviceIds).get(10, TimeUnit.SECONDS);
+ } catch (final Exception e) {
+ return List.of(new ConfigVerificationResult.Builder()
+ .verificationStepName("Record Parsing")
+ .outcome(Outcome.FAILED)
+ .explanation("Failed to enable Controller Services due to " + e)
+ .build());
+ }
+
+ try {
+ final ProcessorFacade consumeKafkaFacade = findProcessors(rootGroup,
+ processor -> processor.getDefinition().getType().endsWith("ConsumeKafka")).getFirst();
+
+ final List<ConfigVerificationResult> configVerificationResults = consumeKafkaFacade.verify(flow, Map.of());
+ for (final ConfigVerificationResult result : configVerificationResults) {
+ if (result.getOutcome() == Outcome.FAILED) {
+ return List.of(result);
+ }
+ }
+
+ return Collections.emptyList();
+ } finally {
+ rootGroup.getLifecycle().disableControllerServices(serviceIds);
+ }
+ }
+
+
+ private List<ConfigVerificationResult> verifyTopicsExists(final FlowContext workingFlowContext) {
+ final List<String> topicsAvailable;
+ try {
+ topicsAvailable = getAvailableTopics(workingFlowContext);
+ } catch (final Exception e) {
+ return List.of(new ConfigVerificationResult.Builder()
+ .verificationStepName("Verify Kafka topics exist")
+ .outcome(Outcome.SKIPPED)
+ .explanation("Unable to validate that topics exist due to " + e)
+ .build());
+ }
+
+ final Set<String> topicNames = new HashSet<>(topicsAvailable);
+ final List<String> specifiedTopics = workingFlowContext.getConfigurationContext().getProperty(KafkaTopicsStep.STEP_NAME,
+ KafkaTopicsStep.KAFKA_TOPICS_GROUP.getName(), KafkaTopicsStep.TOPIC_NAMES.getName()).asList();
+ final String missingTopics = specifiedTopics.stream()
+ .filter(topic -> !topicNames.contains(topic))
+ .collect(Collectors.joining(", "));
+
+ if (!missingTopics.isEmpty()) {
+ return List.of(new ConfigVerificationResult.Builder()
+ .verificationStepName("Verify Kafka topics exist")
+ .outcome(Outcome.FAILED)
+ .explanation("The following topics do not exist in the Kafka cluster: " + missingTopics)
+ .build());
+ } else {
+ return List.of(new ConfigVerificationResult.Builder()
+ .verificationStepName("Verify Kafka topics exist")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("All specified topics exist in the Kafka cluster")
+ .build());
+ }
+ }
+
+ private List<ConfigVerificationResult> verifyKafkaConnectivity(final FlowContext workingFlowContext, final VersionedExternalFlow flow) {
+ // Build a new version of the flow so that we can get the relevant properties of the Kafka Connection Service
+ final ControllerServiceFacade connectionService = getKafkaConnectionService(workingFlowContext);
+ final List<ConfigVerificationResult> configVerificationResults = connectionService.verify(flow, Map.of());
+
+ for (final ConfigVerificationResult result : configVerificationResults) {
+ if (result.getOutcome() == Outcome.FAILED) {
+ return List.of(new ConfigVerificationResult.Builder()
+ .verificationStepName("Verify Kafka connectivity")
+ .outcome(Outcome.FAILED)
+ .explanation(result.getExplanation())
+ .build());
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ private VersionedExternalFlow buildFlow(final ConnectorConfigurationContext configurationContext) {
+ final KafkaToS3FlowBuilder flowBuilder = new KafkaToS3FlowBuilder(configurationContext);
+ return flowBuilder.buildFlow();
+ }
+
+ @Override
+ public List<AllowableValue> fetchAllowableValues(final String stepName, final String groupName, final String propertyName, final FlowContext flowContext) {
+ if (stepName.equals(KafkaTopicsStep.STEP_NAME) && propertyName.equals(KafkaTopicsStep.TOPIC_NAMES.getName())) {
+ return createAllowableValues(getAvailableTopics(flowContext));
+ } else if (stepName.equals(S3Step.S3_STEP_NAME) && propertyName.equals(S3Step.S3_REGION.getName())) {
+ return createAllowableValues(getPossibleS3Regions(flowContext));
+ }
+
+ return super.fetchAllowableValues(stepName, groupName, propertyName, flowContext);
+ }
+
+ private List<AllowableValue> createAllowableValues(final List<String> values) {
+ return values.stream().map(this::createAllowableValue).collect(Collectors.toList());
+ }
+
+ private AllowableValue createAllowableValue(final String value) {
+ return new AllowableValue(value, value, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<String> getAvailableTopics(final FlowContext flowContext) {
+ // If Kafka Brokers not yet set, return empty list
+ final ConnectorConfigurationContext config = flowContext.getConfigurationContext();
+ if (!config.getProperty(KafkaConnectionStep.KAFKA_CONNECTION_STEP, KafkaConnectionStep.KAFKA_SERVER_GROUP, KafkaConnectionStep.KAFKA_BROKERS).isSet()) {
+ return List.of();
+ }
+
+ final ControllerServiceFacade kafkaConnectionService = getKafkaConnectionService(flowContext);
+
+ try {
+ return (List<String>) kafkaConnectionService.invokeConnectorMethod("listTopicNames", Map.of());
+ } catch (final Exception e) {
+ getLogger().warn("Failed to retrieve available Kafka topics", e);
+ return List.of();
+ }
+ }
+
+ private ControllerServiceFacade getKafkaConnectionService(final FlowContext flowContext) {
+ return flowContext.getRootGroup().getControllerServices().stream()
+ .filter(service -> service.getDefinition().getType().endsWith("Kafka3ConnectionService"))
+ .findFirst()
+ .orElseThrow();
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<String> getPossibleS3Regions(final FlowContext flowContext) {
+ final ProcessorFacade processorFacade = flowContext.getRootGroup().getProcessors().stream()
+ .filter(proc -> proc.getDefinition().getType().endsWith("PutS3Object"))
+ .findFirst()
+ .orElseThrow();
+
+ try {
+ return (List<String>) processorFacade.invokeConnectorMethod("getAvailableRegions", Map.of());
+ } catch (final InvocationFailedException e) {
+ getLogger().error("Failed to obtain list of available S3 regions", e);
+ return Collections.emptyList();
+ }
+ }
+}
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3FlowBuilder.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3FlowBuilder.java
new file mode 100644
index 0000000000..88b22df5e3
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaToS3FlowBuilder.java
@@ -0,0 +1,217 @@
+/*
+ * 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.connectors.kafkas3;
+
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorConfigurationContext;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.util.VersionedFlowUtils;
+import org.apache.nifi.flow.VersionedControllerService;
+import org.apache.nifi.flow.VersionedExternalFlow;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.processor.DataUnit;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class KafkaToS3FlowBuilder {
+ private static final String FLOW_JSON_PATH = "flows/Kafka_to_S3.json";
+
+ private final ConnectorConfigurationContext configContext;
+
+ public KafkaToS3FlowBuilder(final ConnectorConfigurationContext configurationContext) {
+ this.configContext = configurationContext;
+ }
+
+ public static VersionedExternalFlow loadInitialFlow() {
+ return VersionedFlowUtils.loadFlowFromResource(FLOW_JSON_PATH);
+ }
+
+ public VersionedExternalFlow buildFlow() {
+ final VersionedExternalFlow externalFlow = VersionedFlowUtils.loadFlowFromResource(FLOW_JSON_PATH);
+ configureSchemaRegistry(externalFlow);
+
+ updateKafkaConnectionParameters(externalFlow);
+ updateSchemaRegistryParameters(externalFlow);
+ updateReaderWriter(externalFlow);
+ updateKafkaTopicsParameters(externalFlow);
+ updateS3Config(externalFlow);
+
+ return externalFlow;
+ }
+
+ private void configureSchemaRegistry(final VersionedExternalFlow externalFlow) {
+ final String schemaRegistryUrl = configContext.getProperty(KafkaConnectionStep.KAFKA_CONNECTION_STEP,
+ KafkaConnectionStep.SCHEMA_REGISTRY_GROUP,
+ KafkaConnectionStep.SCHEMA_REGISTRY_URL).getValue();
+
+ if (schemaRegistryUrl == null) {
+ final VersionedProcessGroup processGroup = externalFlow.getFlowContents();
+
+ // Remove any references to the Schema Registry service.
+ final VersionedControllerService schemaRegistryService = processGroup.getControllerServices().stream()
+ .filter(service -> service.getType().endsWith("ConfluentSchemaRegistry"))
+ .findFirst()
+ .orElseThrow();
+ VersionedFlowUtils.removeControllerServiceReferences(processGroup, schemaRegistryService.getIdentifier());
+
+ final VersionedControllerService schemaReferenceReader = processGroup.getControllerServices().stream()
+ .filter(service -> service.getType().endsWith("ConfluentEncodedSchemaReferenceReader"))
+ .findFirst()
+ .orElseThrow();
+ VersionedFlowUtils.removeControllerServiceReferences(processGroup, schemaReferenceReader.getIdentifier());
+
+ processGroup.getControllerServices().stream()
+ .filter(service -> service.getType().endsWith("JsonTreeReader"))
+ .forEach(service -> service.getProperties().put("Schema Access Strategy", "infer-schema"));
+
+ processGroup.getControllerServices().stream()
+ .filter(service -> service.getType().endsWith("JsonRecordSetWriter"))
+ .forEach(service -> service.getProperties().put("Schema Write Strategy", "no-schema"));
+ }
+ }
+
+ private void updateSchemaRegistryParameters(final VersionedExternalFlow externalFlow) {
+ final ConfigurationStep connectionStep = KafkaConnectionStep.KAFKA_CONNECTION_STEP;
+ final ConnectorPropertyGroup registryGroup = KafkaConnectionStep.SCHEMA_REGISTRY_GROUP;
+
+ final String schemaRegistryUrl = configContext.getProperty(connectionStep, registryGroup, KafkaConnectionStep.SCHEMA_REGISTRY_URL).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Schema Registry URLs", schemaRegistryUrl);
+
+ if (schemaRegistryUrl == null) {
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("schema-access-strategy", "infer-schema");
+ properties.put("schema-registry", null);
+ properties.put("schema-reference-reader", null);
+
+ externalFlow.getFlowContents().getControllerServices().stream()
+ .filter(service -> service.getType().endsWith("JsonTreeReader"))
+ .findFirst()
+ .ifPresent(service -> service.setProperties(properties));
+ } else {
+ final String username = configContext.getProperty(connectionStep, registryGroup, KafkaConnectionStep.SCHEMA_REGISTRY_USERNAME).getValue();
+ final String password = configContext.getProperty(connectionStep, registryGroup, KafkaConnectionStep.SCHEMA_REGISTRY_PASSWORD).getValue();
+
+ VersionedFlowUtils.setParameterValue(externalFlow, "Schema Registry Username", username);
+ VersionedFlowUtils.setParameterValue(externalFlow, "Schema Registry Password", password);
+
+ final String authenticationType = (username == null || username.isEmpty()) ? "NONE" : "BASIC";
+ VersionedFlowUtils.setParameterValue(externalFlow, "Schema Registry Authentication Type", authenticationType);
+ }
+ }
+
+ private void updateKafkaConnectionParameters(final VersionedExternalFlow externalFlow) {
+ final ConfigurationStep connectionStep = KafkaConnectionStep.KAFKA_CONNECTION_STEP;
+ final ConnectorPropertyGroup serverGroup = KafkaConnectionStep.KAFKA_SERVER_GROUP;
+
+ final String kafkaBrokers = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.KAFKA_BROKERS).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Kafka Bootstrap Servers", kafkaBrokers);
+
+ final String securityProtocol = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.SECURITY_PROTOCOL).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Kafka Security Protocol", securityProtocol);
+
+ if (securityProtocol.contains("SASL")) {
+ final String saslMechanism = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.SASL_MECHANISM).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Kafka SASL Mechanism", saslMechanism);
+
+ final String username = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.USERNAME).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Kafka SASL Username", username);
+
+ final String password = configContext.getProperty(connectionStep, serverGroup, KafkaConnectionStep.PASSWORD).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Kafka SASL Password", password);
+ }
+ }
+
+ private void updateReaderWriter(final VersionedExternalFlow externalFlow) {
+ final VersionedProcessGroup rootGroup = externalFlow.getFlowContents();
+
+ final VersionedControllerService avroService = rootGroup.getControllerServices().stream()
+ .filter(service -> service.getType().endsWith("AvroReader"))
+ .findFirst()
+ .orElseThrow();
+
+ final String kafkaDataFormat = configContext.getProperty(KafkaTopicsStep.STEP_NAME, KafkaTopicsStep.KAFKA_TOPICS_GROUP.getName(), KafkaTopicsStep.KAFKA_DATA_FORMAT.getName()).getValue();
+ if (!kafkaDataFormat.equalsIgnoreCase("JSON")) {
+ // Update ConsumeKafka processor to use Avro Reader
+ rootGroup.getProcessors().stream()
+ .filter(versionedProcessor -> versionedProcessor.getType().endsWith("ConsumeKafka"))
+ .findFirst()
+ .ifPresent(processor -> processor.getProperties().put("Record Reader", avroService.getIdentifier()));
+ }
+
+ VersionedFlowUtils.setParameterValue(externalFlow, "Kafka Data Format", kafkaDataFormat);
+
+ // TODO: Have this throw an Exception if GROUP does not contain the given property
+ final String s3DataFormat = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_DATA_FORMAT).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "S3 Data Format", s3DataFormat);
+ }
+
+ private void updateKafkaTopicsParameters(final VersionedExternalFlow externalFlow) {
+ final String groupName = KafkaTopicsStep.KAFKA_TOPICS_GROUP.getName();
+
+ final String topics = configContext.getProperty(KafkaTopicsStep.STEP_NAME, groupName, KafkaTopicsStep.TOPIC_NAMES.getName()).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Topic Names", topics);
+
+ final String groupId = configContext.getProperty(KafkaTopicsStep.STEP_NAME, groupName, KafkaTopicsStep.CONSUMER_GROUP_ID.getName()).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Consumer Group ID", groupId);
+
+ final String offsetReset = configContext.getProperty(KafkaTopicsStep.STEP_NAME, groupName, KafkaTopicsStep.OFFSET_RESET.getName()).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Kafka Auto Offset Reset", offsetReset);
+ }
+
+ private void updateS3Config(final VersionedExternalFlow externalFlow) {
+ final String region = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_REGION).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "S3 Region", region);
+
+ final String bucket = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_BUCKET).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "S3 Bucket", bucket);
+
+ final String prefix = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_PREFIX).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "S3 Prefix", prefix);
+
+ final String endpointOverrideUrl = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_DESTINATION_GROUP, S3Step.S3_ENDPOINT_OVERRIDE_URL).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "S3 Endpoint Override URL", endpointOverrideUrl);
+
+ final String authStrategy = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_CREDENTIALS_GROUP, S3Step.S3_AUTHENTICATION_STRATEGY).getValue();
+ if (authStrategy.equals(S3Step.DEFAULT_CREDENTIALS)) {
+ final VersionedControllerService credentialsService = externalFlow.getFlowContents().getControllerServices().stream()
+ .filter(service -> service.getType().endsWith("AWSCredentialsProviderControllerService"))
+ .findFirst()
+ .orElseThrow();
+
+ credentialsService.setProperties(Map.of("default-credentials", "true"));
+ } else {
+ final String accessKey = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_CREDENTIALS_GROUP, S3Step.S3_ACCESS_KEY_ID).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "S3 Access Key ID", accessKey);
+
+ final String secretKey = configContext.getProperty(S3Step.S3_STEP, S3Step.S3_CREDENTIALS_GROUP, S3Step.S3_SECRET_ACCESS_KEY).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "S3 Secret Access Key", secretKey);
+ }
+
+ final long mergeBytes = configContext.getProperty(S3Step.S3_STEP, S3Step.MERGE_GROUP, S3Step.TARGET_OBJECT_SIZE).asDataSize(DataUnit.B).longValue();
+ final String mergeSize = configContext.getProperty(S3Step.S3_STEP, S3Step.MERGE_GROUP, S3Step.TARGET_OBJECT_SIZE).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Target Object Size", mergeSize);
+
+ // Max Bin size will be either 10% more than target size or target size + 100MB, whichever is smaller
+ final long maxBinSize = (long) Math.min(mergeBytes + 100_000_000, mergeBytes * 1.1D);
+ VersionedFlowUtils.setParameterValue(externalFlow, "Maximum Object Size", maxBinSize + " B");
+
+ final String mergeLatency = configContext.getProperty(S3Step.S3_STEP, S3Step.MERGE_GROUP, S3Step.MERGE_LATENCY).getValue();
+ VersionedFlowUtils.setParameterValue(externalFlow, "Merge Latency", mergeLatency);
+ }
+}
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaTopicsStep.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaTopicsStep.java
new file mode 100644
index 0000000000..643e4e836e
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/KafkaTopicsStep.java
@@ -0,0 +1,85 @@
+/*
+ * 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.connectors.kafkas3;
+
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.PropertyType;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.util.List;
+
+public class KafkaTopicsStep {
+ public static final String STEP_NAME = "Kafka Topics";
+
+ public static final ConnectorPropertyDescriptor TOPIC_NAMES = new ConnectorPropertyDescriptor.Builder()
+ .name("Topic Names")
+ .description("A comma-separated list of Kafka topics to consume from.")
+ .required(true)
+ .type(PropertyType.STRING_LIST)
+ .allowableValuesFetchable(true)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ public static final ConnectorPropertyDescriptor CONSUMER_GROUP_ID = new ConnectorPropertyDescriptor.Builder()
+ .name("Consumer Group ID")
+ .description("The consumer group ID to use when connecting to Kafka.")
+ .required(true)
+ .type(PropertyType.STRING)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ public static final ConnectorPropertyDescriptor OFFSET_RESET = new ConnectorPropertyDescriptor.Builder()
+ .name("Offset Reset")
+ .description("What to do when there is no initial offset in Kafka or if the current offset does not exist any more on the server.")
+ .required(true)
+ .type(PropertyType.STRING)
+ .defaultValue("latest")
+ .allowableValues("earliest", "latest", "none")
+ .build();
+
+ public static final ConnectorPropertyDescriptor KAFKA_DATA_FORMAT = new ConnectorPropertyDescriptor.Builder()
+ .name("Kafka Data Format")
+ .description("The format of the data in Kafka topics.")
+ .required(true)
+ .type(PropertyType.STRING)
+ .defaultValue("Avro")
+ .allowableValues("Avro", "JSON")
+ .build();
+
+ public static final ConnectorPropertyGroup KAFKA_TOPICS_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Kafka Topics Configuration")
+ .description("Properties for configuring Kafka topics consumption.")
+ .properties(List.of(
+ TOPIC_NAMES,
+ CONSUMER_GROUP_ID,
+ OFFSET_RESET,
+ KAFKA_DATA_FORMAT
+ ))
+ .build();
+
+ public static final ConfigurationStep KAFKA_TOPICS_STEP = new ConfigurationStep.Builder()
+ .name(STEP_NAME)
+ .description("Kafka topics to consume from." )
+ .propertyGroups(List.of(
+ KAFKA_TOPICS_GROUP
+ ))
+ .build();
+
+}
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/S3Step.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/S3Step.java
new file mode 100644
index 0000000000..ad29115c4a
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/java/org/apache/nifi/connectors/kafkas3/S3Step.java
@@ -0,0 +1,156 @@
+/*
+ * 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.connectors.kafkas3;
+
+import org.apache.nifi.components.connector.ConfigurationStep;
+import org.apache.nifi.components.connector.ConnectorPropertyDescriptor;
+import org.apache.nifi.components.connector.ConnectorPropertyGroup;
+import org.apache.nifi.components.connector.PropertyType;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.util.List;
+
+public class S3Step {
+ public static final String S3_STEP_NAME = "S3 Configuration";
+ public static final String ACCESS_KEY_ID_SECRET_KEY = "Access Key ID and Secret Key";
+ public static final String DEFAULT_CREDENTIALS = "Default AWS Credentials";
+
+
+ public static final ConnectorPropertyDescriptor S3_BUCKET = new ConnectorPropertyDescriptor.Builder()
+ .name("S3 Bucket")
+ .description("The name of the S3 bucket to write data to.")
+ .required(true)
+ .build();
+
+ public static final ConnectorPropertyDescriptor S3_REGION = new ConnectorPropertyDescriptor.Builder()
+ .name("S3 Region")
+ .description("The AWS region where the S3 bucket is located.")
+ .allowableValuesFetchable(true)
+ .required(true)
+ .build();
+
+ public static final ConnectorPropertyDescriptor S3_DATA_FORMAT = new ConnectorPropertyDescriptor.Builder()
+ .name("S3 Data Format")
+ .description("The format to use when writing data to S3.")
+ .required(true)
+ .defaultValue("JSON")
+ .allowableValues("Avro", "JSON")
+ .build();
+
+ public static final ConnectorPropertyDescriptor S3_PREFIX = new ConnectorPropertyDescriptor.Builder()
+ .name("S3 Prefix")
+ .description("An optional prefix to prepend to all object keys written to the S3 bucket.")
+ .required(false)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ public static final ConnectorPropertyDescriptor S3_ENDPOINT_OVERRIDE_URL = new ConnectorPropertyDescriptor.Builder()
+ .name("S3 Endpoint Override URL")
+ .description("An optional endpoint URL to use instead of the default AWS S3 endpoint. " +
+ "This can be used to connect to S3-compatible storage systems but should be left unset for connecting to S3.")
+ .required(false)
+ .addValidator(StandardValidators.URL_VALIDATOR)
+ .build();
+
+ public static final ConnectorPropertyDescriptor TARGET_OBJECT_SIZE = new ConnectorPropertyDescriptor.Builder()
+ .name("Target Object Size")
+ .description("The target size for each object written to S3. The connector will attempt to " +
+ "combine messages until this size is reached before writing an object to S3. Note that this size is approximate " +
+ "and may vary from object to object.")
+ .required(true)
+ .defaultValue("256 MB")
+ .addValidator(StandardValidators.DATA_SIZE_VALIDATOR)
+ .build();
+
+ public static final ConnectorPropertyDescriptor MERGE_LATENCY = new ConnectorPropertyDescriptor.Builder()
+ .name("Merge Latency")
+ .description("The maximum amount of time to wait while merging messages before writing an object to S3. " +
+ "If this time is reached before the target object size is met, the current set of merged messages " +
+ "will be written to S3.")
+ .required(true)
+ .defaultValue("5 min")
+ .addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
+ .build();
+
+ public static final ConnectorPropertyDescriptor S3_AUTHENTICATION_STRATEGY = new ConnectorPropertyDescriptor.Builder()
+ .name("S3 Authentication Strategy")
+ .description("The authentication strategy to use when connecting to S3.")
+ .required(true)
+ .defaultValue(ACCESS_KEY_ID_SECRET_KEY)
+ .allowableValues(ACCESS_KEY_ID_SECRET_KEY, DEFAULT_CREDENTIALS)
+ .build();
+
+ public static final ConnectorPropertyDescriptor S3_ACCESS_KEY_ID = new ConnectorPropertyDescriptor.Builder()
+ .name("S3 Access Key ID")
+ .description("The AWS Access Key ID used to authenticate to S3.")
+ .required(true)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .dependsOn(S3_AUTHENTICATION_STRATEGY, ACCESS_KEY_ID_SECRET_KEY)
+ .build();
+
+ public static final ConnectorPropertyDescriptor S3_SECRET_ACCESS_KEY = new ConnectorPropertyDescriptor.Builder()
+ .name("S3 Secret Access Key")
+ .description("The AWS Secret Access Key used to authenticate to S3.")
+ .required(true)
+ .type(PropertyType.SECRET)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .dependsOn(S3_AUTHENTICATION_STRATEGY, ACCESS_KEY_ID_SECRET_KEY)
+ .build();
+
+
+ public static final ConnectorPropertyGroup S3_DESTINATION_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("S3 Destination Configuration")
+ .description("Properties required to connect to S3 and specify the target bucket.")
+ .properties(List.of(
+ S3_BUCKET,
+ S3_PREFIX,
+ S3_REGION,
+ S3_DATA_FORMAT,
+ S3_ENDPOINT_OVERRIDE_URL
+ ))
+ .build();
+
+ public static final ConnectorPropertyGroup S3_CREDENTIALS_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("S3 Credentials")
+ .description("Properties required to authenticate to S3.")
+ .properties(List.of(
+ S3_AUTHENTICATION_STRATEGY,
+ S3_ACCESS_KEY_ID,
+ S3_SECRET_ACCESS_KEY
+ ))
+ .build();
+
+ public static final ConnectorPropertyGroup MERGE_GROUP = new ConnectorPropertyGroup.Builder()
+ .name("Merge Configuration")
+ .description("Configuration for how data should be merged together before being persisted to S3.")
+ .properties(List.of(
+ TARGET_OBJECT_SIZE,
+ MERGE_LATENCY
+ ))
+ .build();
+
+ public static final ConfigurationStep S3_STEP = new ConfigurationStep.Builder()
+ .name(S3_STEP_NAME)
+ .description("Configure connection to S3 and target bucket details.")
+ .propertyGroups(List.of(
+ S3_DESTINATION_GROUP,
+ MERGE_GROUP,
+ S3_CREDENTIALS_GROUP
+ ))
+ .build();
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
similarity index 80%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
copy to nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
index 56740ff7c3..de5fe10a48 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/META-INF/services/org.apache.nifi.components.connector.Connector
@@ -12,6 +12,5 @@
# 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.
-org.apache.nifi.test.processors.ModifiesClasspathProcessor
-org.apache.nifi.test.processors.ModifiesClasspathNoAnnotationProcessor
-org.apache.nifi.controller.service.mock.DummyProcessor
\ No newline at end of file
+
+org.apache.nifi.connectors.kafkas3.KafkaToS3
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/flows/Kafka_to_S3.json b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/flows/Kafka_to_S3.json
new file mode 100644
index 0000000000..5df6da47ae
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-connector/src/main/resources/flows/Kafka_to_S3.json
@@ -0,0 +1,2105 @@
+{
+ "flowContents": {
+ "identifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b",
+ "instanceIdentifier": "2c825ae7-019a-1000-f3b7-8275175b7b5b",
+ "name": "Kafka to S3",
+ "comments": "",
+ "position": {
+ "x": -355.0,
+ "y": -238.5
+ },
+ "processGroups": [],
+ "remoteProcessGroups": [],
+ "processors": [
+ {
+ "identifier": "4ce9587d-a171-3dff-bea8-bc67f4b356cd",
+ "instanceIdentifier": "40e02539-8a55-3a44-53d9-e9d41f7db23a",
+ "name": "MergeRecord",
+ "comments": "",
+ "position": {
+ "x": -250.0,
+ "y": -55.5
+ },
+ "type": "org.apache.nifi.processors.standard.MergeRecord",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "max-bin-age": "#{Merge Latency}",
+ "correlation-attribute-name": "kafka.topic",
+ "Attribute Strategy": "Keep Only Common Attributes",
+ "min-records": "1",
+ "max-bin-size": "#{Maximum Object size}",
+ "record-writer": "2384a9ae-311e-3d5f-983b-13f82174244c",
+ "merge-strategy": "Bin-Packing Algorithm",
+ "record-reader": "cd697ee5-9621-3680-a6b8-af3237472b5b",
+ "max-records": "1000000000",
+ "min-bin-size": "#{Target Object Size}",
+ "max.bin.count": "10"
+ },
+ "propertyDescriptors": {
+ "max-bin-age": {
+ "name": "max-bin-age",
+ "displayName": "Max Bin Age",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "correlation-attribute-name": {
+ "name": "correlation-attribute-name",
+ "displayName": "Correlation Attribute Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Attribute Strategy": {
+ "name": "Attribute Strategy",
+ "displayName": "Attribute Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "min-records": {
+ "name": "min-records",
+ "displayName": "Minimum Number of Records",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "max-bin-size": {
+ "name": "max-bin-size",
+ "displayName": "Maximum Bin Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "record-writer": {
+ "name": "record-writer",
+ "displayName": "Record Writer",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "merge-strategy": {
+ "name": "merge-strategy",
+ "displayName": "Merge Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "record-reader": {
+ "name": "record-reader",
+ "displayName": "Record Reader",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "max-records": {
+ "name": "max-records",
+ "displayName": "Maximum Number of Records",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "min-bin-size": {
+ "name": "min-bin-size",
+ "displayName": "Minimum Bin Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "max.bin.count": {
+ "name": "max.bin.count",
+ "displayName": "Maximum Number of Bins",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "style": {},
+ "schedulingPeriod": "0 sec",
+ "schedulingStrategy": "TIMER_DRIVEN",
+ "executionNode": "ALL",
+ "penaltyDuration": "30 sec",
+ "yieldDuration": "1 sec",
+ "bulletinLevel": "WARN",
+ "runDurationMillis": 0,
+ "concurrentlySchedulableTaskCount": 1,
+ "autoTerminatedRelationships": [
+ "original",
+ "failure"
+ ],
+ "scheduledState": "ENABLED",
+ "retryCount": 10000,
+ "retriedRelationships": [
+ "failure"
+ ],
+ "backoffMechanism": "PENALIZE_FLOWFILE",
+ "maxBackoffPeriod": "10 mins",
+ "componentType": "PROCESSOR",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "e427a2b2-355c-3ede-9749-b3e4a8561d22",
+ "instanceIdentifier": "e048d449-7da8-30f3-8ff4-71c87c1d73db",
+ "name": "ConsumeKafka",
+ "comments": "",
+ "position": {
+ "x": -250.0,
+ "y": -268.5
+ },
+ "type": "org.apache.nifi.kafka.processors.ConsumeKafka",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-kafka-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "Topics": "#{Topic Names}",
+ "Commit Offsets": "true",
+ "Header Name Pattern": null,
+ "Key Format": "byte-array",
+ "Key Record Reader": null,
+ "Message Demarcator": null,
+ "Record Reader": "919b2a35-4bc7-380c-b592-6ad2ffffbed5",
+ "Key Attribute Encoding": "utf-8",
+ "Max Uncommitted Size": null,
+ "Topic Format": "names",
+ "Header Encoding": "UTF-8",
+ "Max Uncommitted Time": "5 secs",
+ "Kafka Connection Service": "ccf048dc-004a-35be-8e4d-c7a763a30b02",
+ "Separate By Key": "false",
+ "Processing Strategy": "RECORD",
+ "Record Writer": "2384a9ae-311e-3d5f-983b-13f82174244c",
+ "Group ID": "#{Consumer Group ID}",
+ "auto.offset.reset": "#{Kafka Auto Offset Reset}",
+ "Output Strategy": "USE_VALUE"
+ },
+ "propertyDescriptors": {
+ "Topics": {
+ "name": "Topics",
+ "displayName": "Topics",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Commit Offsets": {
+ "name": "Commit Offsets",
+ "displayName": "Commit Offsets",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Header Name Pattern": {
+ "name": "Header Name Pattern",
+ "displayName": "Header Name Pattern",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Key Format": {
+ "name": "Key Format",
+ "displayName": "Key Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Key Record Reader": {
+ "name": "Key Record Reader",
+ "displayName": "Key Record Reader",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Message Demarcator": {
+ "name": "Message Demarcator",
+ "displayName": "Message Demarcator",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Record Reader": {
+ "name": "Record Reader",
+ "displayName": "Record Reader",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Key Attribute Encoding": {
+ "name": "Key Attribute Encoding",
+ "displayName": "Key Attribute Encoding",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Max Uncommitted Size": {
+ "name": "Max Uncommitted Size",
+ "displayName": "Max Uncommitted Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Topic Format": {
+ "name": "Topic Format",
+ "displayName": "Topic Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Header Encoding": {
+ "name": "Header Encoding",
+ "displayName": "Header Encoding",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Max Uncommitted Time": {
+ "name": "Max Uncommitted Time",
+ "displayName": "Max Uncommitted Time",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Kafka Connection Service": {
+ "name": "Kafka Connection Service",
+ "displayName": "Kafka Connection Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Separate By Key": {
+ "name": "Separate By Key",
+ "displayName": "Separate By Key",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Processing Strategy": {
+ "name": "Processing Strategy",
+ "displayName": "Processing Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Record Writer": {
+ "name": "Record Writer",
+ "displayName": "Record Writer",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Group ID": {
+ "name": "Group ID",
+ "displayName": "Group ID",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "auto.offset.reset": {
+ "name": "auto.offset.reset",
+ "displayName": "Auto Offset Reset",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Output Strategy": {
+ "name": "Output Strategy",
+ "displayName": "Output Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "style": {},
+ "schedulingPeriod": "0 sec",
+ "schedulingStrategy": "TIMER_DRIVEN",
+ "executionNode": "ALL",
+ "penaltyDuration": "30 sec",
+ "yieldDuration": "1 sec",
+ "bulletinLevel": "WARN",
+ "runDurationMillis": 0,
+ "concurrentlySchedulableTaskCount": 1,
+ "autoTerminatedRelationships": [
+ "parse failure"
+ ],
+ "scheduledState": "ENABLED",
+ "retryCount": 10,
+ "retriedRelationships": [],
+ "backoffMechanism": "PENALIZE_FLOWFILE",
+ "maxBackoffPeriod": "10 mins",
+ "componentType": "PROCESSOR",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "501f25bb-cef8-328f-bfc2-fe58ad12e7b6",
+ "instanceIdentifier": "3de30158-1d3e-38d9-6e68-9578471aa26d",
+ "name": "PutS3Object",
+ "comments": "",
+ "position": {
+ "x": -250.0,
+ "y": 169.5
+ },
+ "type": "org.apache.nifi.processors.aws.s3.PutS3Object",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-aws-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "FullControl User List": "${s3.permissions.full.users}",
+ "Owner": "${s3.owner}",
+ "proxy-configuration-service": null,
+ "Resource Transfer Source": "FLOWFILE_CONTENT",
+ "Cache Control": null,
+ "Endpoint Override URL": "#{S3 Endpoint Override URL}",
+ "File Resource Service": null,
+ "Multipart Upload Max Age Threshold": "7 days",
+ "Object Tags Prefix": null,
+ "Signer Override": "Default Signature",
+ "Content Type": "${mime.type}",
+ "Canned ACL": "${s3.permissions.cannedacl}",
+ "Use Path Style Access": "false",
+ "Encryption Service": null,
+ "Write ACL User List": "${s3.permissions.writeacl.users}",
+ "Use Chunked Encoding": "true",
+ "Read ACL User List": "${s3.permissions.readacl.users}",
+ "Content Disposition": null,
+ "Storage Class": "Standard",
+ "Multipart Part Size": "5 GB",
+ "Remove Tag Prefix": "false",
+ "Object Key": "${#{S3 Prefix}:append(\"/\"):replaceAll('^/$', '')}${filename}",
+ "AWS Credentials Provider service": "3f5da96c-9c54-39aa-8c84-e8098a44dfab",
+ "Multipart Threshold": "5 GB",
+ "Temporary Directory Multipart State": "${java.io.tmpdir}",
+ "Custom Signer Module Location": null,
+ "SSL Context Service": null,
+ "Server Side Encryption": "None",
+ "Bucket": "#{S3 Bucket}",
+ "Multipart Upload AgeOff Interval": "60 min",
+ "Write Permission User List": "${s3.permissions.write.users}",
+ "Communications Timeout": "30 secs",
+ "Region": "#{S3 Region}",
+ "Read Permission User List": "${s3.permissions.read.users}",
+ "Custom Signer Class Name": null,
+ "Expiration Time Rule": null
+ },
+ "propertyDescriptors": {
+ "FullControl User List": {
+ "name": "FullControl User List",
+ "displayName": "FullControl User List",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Owner": {
+ "name": "Owner",
+ "displayName": "Owner",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "proxy-configuration-service": {
+ "name": "proxy-configuration-service",
+ "displayName": "Proxy Configuration Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Resource Transfer Source": {
+ "name": "Resource Transfer Source",
+ "displayName": "Resource Transfer Source",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Cache Control": {
+ "name": "Cache Control",
+ "displayName": "Cache Control",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Endpoint Override URL": {
+ "name": "Endpoint Override URL",
+ "displayName": "Endpoint Override URL",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "File Resource Service": {
+ "name": "File Resource Service",
+ "displayName": "File Resource Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Multipart Upload Max Age Threshold": {
+ "name": "Multipart Upload Max Age Threshold",
+ "displayName": "Multipart Upload Max Age Threshold",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Object Tags Prefix": {
+ "name": "Object Tags Prefix",
+ "displayName": "Object Tags Prefix",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Signer Override": {
+ "name": "Signer Override",
+ "displayName": "Signer Override",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Content Type": {
+ "name": "Content Type",
+ "displayName": "Content Type",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Canned ACL": {
+ "name": "Canned ACL",
+ "displayName": "Canned ACL",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Use Path Style Access": {
+ "name": "Use Path Style Access",
+ "displayName": "Use Path Style Access",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Encryption Service": {
+ "name": "Encryption Service",
+ "displayName": "Encryption Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Write ACL User List": {
+ "name": "Write ACL User List",
+ "displayName": "Write ACL User List",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Use Chunked Encoding": {
+ "name": "Use Chunked Encoding",
+ "displayName": "Use Chunked Encoding",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Read ACL User List": {
+ "name": "Read ACL User List",
+ "displayName": "Read ACL User List",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Content Disposition": {
+ "name": "Content Disposition",
+ "displayName": "Content Disposition",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Storage Class": {
+ "name": "Storage Class",
+ "displayName": "Storage Class",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Multipart Part Size": {
+ "name": "Multipart Part Size",
+ "displayName": "Multipart Part Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Remove Tag Prefix": {
+ "name": "Remove Tag Prefix",
+ "displayName": "Remove Tag Prefix",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Object Key": {
+ "name": "Object Key",
+ "displayName": "Object Key",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "AWS Credentials Provider service": {
+ "name": "AWS Credentials Provider service",
+ "displayName": "AWS Credentials Provider service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Multipart Threshold": {
+ "name": "Multipart Threshold",
+ "displayName": "Multipart Threshold",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Temporary Directory Multipart State": {
+ "name": "Temporary Directory Multipart State",
+ "displayName": "Temporary Directory Multipart State",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Custom Signer Module Location": {
+ "name": "Custom Signer Module Location",
+ "displayName": "Custom Signer Module Location",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false,
+ "resourceDefinition": {
+ "cardinality": "MULTIPLE",
+ "resourceTypes": [
+ "FILE",
+ "DIRECTORY"
+ ]
+ }
+ },
+ "SSL Context Service": {
+ "name": "SSL Context Service",
+ "displayName": "SSL Context Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Server Side Encryption": {
+ "name": "Server Side Encryption",
+ "displayName": "Server Side Encryption",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Bucket": {
+ "name": "Bucket",
+ "displayName": "Bucket",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Multipart Upload AgeOff Interval": {
+ "name": "Multipart Upload AgeOff Interval",
+ "displayName": "Multipart Upload AgeOff Interval",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Write Permission User List": {
+ "name": "Write Permission User List",
+ "displayName": "Write Permission User List",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Communications Timeout": {
+ "name": "Communications Timeout",
+ "displayName": "Communications Timeout",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Region": {
+ "name": "Region",
+ "displayName": "Region",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Read Permission User List": {
+ "name": "Read Permission User List",
+ "displayName": "Read Permission User List",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Custom Signer Class Name": {
+ "name": "Custom Signer Class Name",
+ "displayName": "Custom Signer Class Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Expiration Time Rule": {
+ "name": "Expiration Time Rule",
+ "displayName": "Expiration Time Rule",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "style": {},
+ "schedulingPeriod": "0 sec",
+ "schedulingStrategy": "TIMER_DRIVEN",
+ "executionNode": "ALL",
+ "penaltyDuration": "30 sec",
+ "yieldDuration": "1 sec",
+ "bulletinLevel": "WARN",
+ "runDurationMillis": 0,
+ "concurrentlySchedulableTaskCount": 1,
+ "autoTerminatedRelationships": [
+ "success",
+ "failure"
+ ],
+ "scheduledState": "ENABLED",
+ "retryCount": 100000,
+ "retriedRelationships": [
+ "failure"
+ ],
+ "backoffMechanism": "PENALIZE_FLOWFILE",
+ "maxBackoffPeriod": "10 mins",
+ "componentType": "PROCESSOR",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ }
+ ],
+ "inputPorts": [],
+ "outputPorts": [],
+ "connections": [
+ {
+ "identifier": "4bd001fc-7fa6-3c34-b297-bae4987889ad",
+ "instanceIdentifier": "d6a7b460-571b-39b3-b55f-7d82f62b397a",
+ "name": "",
+ "source": {
+ "id": "e427a2b2-355c-3ede-9749-b3e4a8561d22",
+ "type": "PROCESSOR",
+ "groupId": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b",
+ "name": "ConsumeKafka",
+ "comments": "",
+ "instanceIdentifier": "e048d449-7da8-30f3-8ff4-71c87c1d73db"
+ },
+ "destination": {
+ "id": "4ce9587d-a171-3dff-bea8-bc67f4b356cd",
+ "type": "PROCESSOR",
+ "groupId": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b",
+ "name": "MergeRecord",
+ "comments": "",
+ "instanceIdentifier": "40e02539-8a55-3a44-53d9-e9d41f7db23a"
+ },
+ "labelIndex": 0,
+ "zIndex": 1,
+ "selectedRelationships": [
+ "success"
+ ],
+ "backPressureObjectThreshold": 10000,
+ "backPressureDataSizeThreshold": "1 GB",
+ "flowFileExpiration": "0 sec",
+ "prioritizers": [],
+ "bends": [],
+ "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE",
+ "partitioningAttribute": "",
+ "loadBalanceCompression": "DO_NOT_COMPRESS",
+ "componentType": "CONNECTION",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "08803783-a109-3362-9177-0f2016086a2a",
+ "instanceIdentifier": "45276df0-1076-39a8-7d84-097b7f593486",
+ "name": "",
+ "source": {
+ "id": "4ce9587d-a171-3dff-bea8-bc67f4b356cd",
+ "type": "PROCESSOR",
+ "groupId": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b",
+ "name": "MergeRecord",
+ "comments": "",
+ "instanceIdentifier": "40e02539-8a55-3a44-53d9-e9d41f7db23a"
+ },
+ "destination": {
+ "id": "501f25bb-cef8-328f-bfc2-fe58ad12e7b6",
+ "type": "PROCESSOR",
+ "groupId": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b",
+ "name": "PutS3Object",
+ "comments": "",
+ "instanceIdentifier": "3de30158-1d3e-38d9-6e68-9578471aa26d"
+ },
+ "labelIndex": 0,
+ "zIndex": 2,
+ "selectedRelationships": [
+ "merged"
+ ],
+ "backPressureObjectThreshold": 10000,
+ "backPressureDataSizeThreshold": "1 GB",
+ "flowFileExpiration": "0 sec",
+ "prioritizers": [],
+ "bends": [],
+ "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE",
+ "partitioningAttribute": "",
+ "loadBalanceCompression": "DO_NOT_COMPRESS",
+ "componentType": "CONNECTION",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ }
+ ],
+ "labels": [],
+ "funnels": [],
+ "controllerServices": [
+ {
+ "identifier": "cd697ee5-9621-3680-a6b8-af3237472b5b",
+ "instanceIdentifier": "2cdfec74-019a-1000-2786-edf80b919dfc",
+ "name": "Merge JSON Reader",
+ "comments": "",
+ "type": "org.apache.nifi.json.JsonTreeReader",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-record-serialization-services-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "Schema Branch": null,
+ "Schema Reference Reader": null,
+ "Schema Text": "${avro.schema}",
+ "Max String Length": "20 MB",
+ "schema-application-strategy": "SELECTED_PART",
+ "Schema Registry": "34d29160-0744-373c-a1c7-39c548e7e6b1",
+ "Schema Access Strategy": "schema-name",
+ "Schema Name": "${schema.name}",
+ "Timestamp Format": null,
+ "schema-inference-cache": null,
+ "Date Format": null,
+ "starting-field-strategy": "ROOT_NODE",
+ "Schema Version": null,
+ "starting-field-name": null,
+ "Time Format": null,
+ "Allow Comments": "false"
+ },
+ "propertyDescriptors": {
+ "Schema Branch": {
+ "name": "Schema Branch",
+ "displayName": "Schema Branch",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Reference Reader": {
+ "name": "Schema Reference Reader",
+ "displayName": "Schema Reference Reader",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Text": {
+ "name": "Schema Text",
+ "displayName": "Schema Text",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Max String Length": {
+ "name": "Max String Length",
+ "displayName": "Max String Length",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "schema-application-strategy": {
+ "name": "schema-application-strategy",
+ "displayName": "Schema Application Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Registry": {
+ "name": "Schema Registry",
+ "displayName": "Schema Registry",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Access Strategy": {
+ "name": "Schema Access Strategy",
+ "displayName": "Schema Access Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Name": {
+ "name": "Schema Name",
+ "displayName": "Schema Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Timestamp Format": {
+ "name": "Timestamp Format",
+ "displayName": "Timestamp Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "schema-inference-cache": {
+ "name": "schema-inference-cache",
+ "displayName": "Schema Inference Cache",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Date Format": {
+ "name": "Date Format",
+ "displayName": "Date Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "starting-field-strategy": {
+ "name": "starting-field-strategy",
+ "displayName": "Starting Field Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Version": {
+ "name": "Schema Version",
+ "displayName": "Schema Version",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "starting-field-name": {
+ "name": "starting-field-name",
+ "displayName": "Starting Field Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Time Format": {
+ "name": "Time Format",
+ "displayName": "Time Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Allow Comments": {
+ "name": "Allow Comments",
+ "displayName": "Allow Comments",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.serialization.RecordReaderFactory",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "b01590c6-5149-3c50-9d16-3a8e7b1bcdf6",
+ "instanceIdentifier": "2f0a61a3-b66d-3969-09d7-df35dd0b2740",
+ "name": "AvroRecordSetWriter",
+ "comments": "",
+ "type": "org.apache.nifi.avro.AvroRecordSetWriter",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-record-serialization-services-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "Schema Branch": null,
+ "Schema Reference Reader": null,
+ "Schema Text": "${avro.schema}",
+ "Schema Registry": null,
+ "Schema Access Strategy": "inherit-record-schema",
+ "Schema Name": "${schema.name}",
+ "compression-format": "NONE",
+ "Schema Write Strategy": "avro-embedded",
+ "cache-size": "1000",
+ "Schema Cache": null,
+ "Schema Version": null,
+ "encoder-pool-size": "32",
+ "Schema Reference Writer": null
+ },
+ "propertyDescriptors": {
+ "Schema Branch": {
+ "name": "Schema Branch",
+ "displayName": "Schema Branch",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Reference Reader": {
+ "name": "Schema Reference Reader",
+ "displayName": "Schema Reference Reader",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Text": {
+ "name": "Schema Text",
+ "displayName": "Schema Text",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Registry": {
+ "name": "Schema Registry",
+ "displayName": "Schema Registry",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Access Strategy": {
+ "name": "Schema Access Strategy",
+ "displayName": "Schema Access Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Name": {
+ "name": "Schema Name",
+ "displayName": "Schema Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "compression-format": {
+ "name": "compression-format",
+ "displayName": "Compression Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Write Strategy": {
+ "name": "Schema Write Strategy",
+ "displayName": "Schema Write Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "cache-size": {
+ "name": "cache-size",
+ "displayName": "Cache Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Cache": {
+ "name": "Schema Cache",
+ "displayName": "Schema Cache",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Version": {
+ "name": "Schema Version",
+ "displayName": "Schema Version",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "encoder-pool-size": {
+ "name": "encoder-pool-size",
+ "displayName": "Encoder Pool Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Reference Writer": {
+ "name": "Schema Reference Writer",
+ "displayName": "Schema Reference Writer",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.serialization.RecordSetWriterFactory",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "919b2a35-4bc7-380c-b592-6ad2ffffbed5",
+ "instanceIdentifier": "c169a376-4b21-3062-e8fb-1d243dd0e5df",
+ "name": "Kafka JSON Reader",
+ "comments": "",
+ "type": "org.apache.nifi.json.JsonTreeReader",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-record-serialization-services-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "Schema Branch": null,
+ "Schema Reference Reader": "ecba6fe5-6beb-3b68-bad9-163a3f14c4c8",
+ "Schema Text": "${avro.schema}",
+ "Max String Length": "20 MB",
+ "schema-application-strategy": "SELECTED_PART",
+ "Schema Registry": "34d29160-0744-373c-a1c7-39c548e7e6b1",
+ "Schema Access Strategy": "schema-reference-reader",
+ "Schema Name": "${schema.name}",
+ "Timestamp Format": null,
+ "schema-inference-cache": null,
+ "Date Format": null,
+ "starting-field-strategy": "ROOT_NODE",
+ "Schema Version": null,
+ "starting-field-name": null,
+ "Time Format": null,
+ "Allow Comments": "false"
+ },
+ "propertyDescriptors": {
+ "Schema Branch": {
+ "name": "Schema Branch",
+ "displayName": "Schema Branch",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Reference Reader": {
+ "name": "Schema Reference Reader",
+ "displayName": "Schema Reference Reader",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Text": {
+ "name": "Schema Text",
+ "displayName": "Schema Text",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Max String Length": {
+ "name": "Max String Length",
+ "displayName": "Max String Length",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "schema-application-strategy": {
+ "name": "schema-application-strategy",
+ "displayName": "Schema Application Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Registry": {
+ "name": "Schema Registry",
+ "displayName": "Schema Registry",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Access Strategy": {
+ "name": "Schema Access Strategy",
+ "displayName": "Schema Access Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Name": {
+ "name": "Schema Name",
+ "displayName": "Schema Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Timestamp Format": {
+ "name": "Timestamp Format",
+ "displayName": "Timestamp Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "schema-inference-cache": {
+ "name": "schema-inference-cache",
+ "displayName": "Schema Inference Cache",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Date Format": {
+ "name": "Date Format",
+ "displayName": "Date Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "starting-field-strategy": {
+ "name": "starting-field-strategy",
+ "displayName": "Starting Field Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Version": {
+ "name": "Schema Version",
+ "displayName": "Schema Version",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "starting-field-name": {
+ "name": "starting-field-name",
+ "displayName": "Starting Field Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Time Format": {
+ "name": "Time Format",
+ "displayName": "Time Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Allow Comments": {
+ "name": "Allow Comments",
+ "displayName": "Allow Comments",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.serialization.RecordReaderFactory",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "174d0040-161d-39dc-8da9-0f127fc46373",
+ "instanceIdentifier": "bf48cf99-1cae-375b-bfe1-bd9aa7368984",
+ "name": "AvroReader",
+ "comments": "",
+ "type": "org.apache.nifi.avro.AvroReader",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-record-serialization-services-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "Schema Branch": null,
+ "Schema Reference Reader": "ecba6fe5-6beb-3b68-bad9-163a3f14c4c8",
+ "Schema Text": "${avro.schema}",
+ "cache-size": "1000",
+ "Schema Version": null,
+ "Schema Registry": "34d29160-0744-373c-a1c7-39c548e7e6b1",
+ "Schema Access Strategy": "schema-reference-reader",
+ "Schema Name": "${schema.name}"
+ },
+ "propertyDescriptors": {
+ "Schema Branch": {
+ "name": "Schema Branch",
+ "displayName": "Schema Branch",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Reference Reader": {
+ "name": "Schema Reference Reader",
+ "displayName": "Schema Reference Reader",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Text": {
+ "name": "Schema Text",
+ "displayName": "Schema Text",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "cache-size": {
+ "name": "cache-size",
+ "displayName": "Cache Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Version": {
+ "name": "Schema Version",
+ "displayName": "Schema Version",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Registry": {
+ "name": "Schema Registry",
+ "displayName": "Schema Registry",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Access Strategy": {
+ "name": "Schema Access Strategy",
+ "displayName": "Schema Access Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Name": {
+ "name": "Schema Name",
+ "displayName": "Schema Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.serialization.RecordReaderFactory",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "3f5da96c-9c54-39aa-8c84-e8098a44dfab",
+ "instanceIdentifier": "7ea5a25b-89be-3652-9778-e30a69f84806",
+ "name": "AWSCredentialsProviderControllerService",
+ "comments": "",
+ "type": "org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderControllerService",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-aws-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "Assume Role STS Region": "us-west-2",
+ "Assume Role Proxy Configuration Service": null,
+ "Access Key ID": "#{S3 Access Key ID}",
+ "Assume Role ARN": null,
+ "Assume Role Session Time": "3600",
+ "Assume Role Session Name": null,
+ "Custom Signer Module Location": null,
+ "Use Default Credentials": "false",
+ "Assume Role External ID": null,
+ "Assume Role STS Endpoint Override": null,
+ "Profile Name": null,
+ "Credentials File": null,
+ "Custom Signer Class Name": null,
+ "Assume Role STS Signer Override": "Default Signature",
+ "Use Anonymous Credentials": "false",
+ "Secret Access Key": "#{S3 Secret Access Key}",
+ "Assume Role SSL Context Service": null
+ },
+ "propertyDescriptors": {
+ "Assume Role STS Region": {
+ "name": "Assume Role STS Region",
+ "displayName": "Assume Role STS Region",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Assume Role Proxy Configuration Service": {
+ "name": "Assume Role Proxy Configuration Service",
+ "displayName": "Assume Role Proxy Configuration Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Access Key ID": {
+ "name": "Access Key ID",
+ "displayName": "Access Key ID",
+ "identifiesControllerService": false,
+ "sensitive": true,
+ "dynamic": false
+ },
+ "Assume Role ARN": {
+ "name": "Assume Role ARN",
+ "displayName": "Assume Role ARN",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Assume Role Session Time": {
+ "name": "Assume Role Session Time",
+ "displayName": "Assume Role Session Time",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Assume Role Session Name": {
+ "name": "Assume Role Session Name",
+ "displayName": "Assume Role Session Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Custom Signer Module Location": {
+ "name": "Custom Signer Module Location",
+ "displayName": "Custom Signer Module Location",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false,
+ "resourceDefinition": {
+ "cardinality": "MULTIPLE",
+ "resourceTypes": [
+ "FILE",
+ "DIRECTORY"
+ ]
+ }
+ },
+ "Use Default Credentials": {
+ "name": "Use Default Credentials",
+ "displayName": "Use Default Credentials",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Assume Role External ID": {
+ "name": "Assume Role External ID",
+ "displayName": "Assume Role External ID",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Assume Role STS Endpoint Override": {
+ "name": "Assume Role STS Endpoint Override",
+ "displayName": "Assume Role STS Endpoint Override",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Profile Name": {
+ "name": "Profile Name",
+ "displayName": "Profile Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Credentials File": {
+ "name": "Credentials File",
+ "displayName": "Credentials File",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false,
+ "resourceDefinition": {
+ "cardinality": "SINGLE",
+ "resourceTypes": [
+ "FILE"
+ ]
+ }
+ },
+ "Custom Signer Class Name": {
+ "name": "Custom Signer Class Name",
+ "displayName": "Custom Signer Class Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Assume Role STS Signer Override": {
+ "name": "Assume Role STS Signer Override",
+ "displayName": "Assume Role STS Signer Override",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Use Anonymous Credentials": {
+ "name": "Use Anonymous Credentials",
+ "displayName": "Use Anonymous Credentials",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Secret Access Key": {
+ "name": "Secret Access Key",
+ "displayName": "Secret Access Key",
+ "identifiesControllerService": false,
+ "sensitive": true,
+ "dynamic": false
+ },
+ "Assume Role SSL Context Service": {
+ "name": "Assume Role SSL Context Service",
+ "displayName": "Assume Role SSL Context Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.processors.aws.credentials.provider.service.AWSCredentialsProviderService",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-aws-service-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ },
+ {
+ "type": "org.apache.nifi.processors.aws.credentials.provider.AwsCredentialsProviderService",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-aws-service-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "2384a9ae-311e-3d5f-983b-13f82174244c",
+ "instanceIdentifier": "d02eb508-0c95-31df-b86b-e1adadd77845",
+ "name": "JsonRecordSetWriter",
+ "comments": "",
+ "type": "org.apache.nifi.json.JsonRecordSetWriter",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-record-serialization-services-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "Schema Branch": null,
+ "Schema Reference Reader": null,
+ "Schema Text": "${avro.schema}",
+ "Allow Scientific Notation": "false",
+ "compression-level": "1",
+ "Schema Registry": null,
+ "Schema Access Strategy": "inherit-record-schema",
+ "Schema Name": "${schema.name}",
+ "Timestamp Format": null,
+ "Date Format": null,
+ "Pretty Print JSON": "false",
+ "compression-format": "none",
+ "Schema Write Strategy": "schema-name",
+ "suppress-nulls": "never-suppress",
+ "output-grouping": "output-oneline",
+ "Schema Cache": null,
+ "Schema Version": null,
+ "Time Format": null,
+ "Schema Reference Writer": null
+ },
+ "propertyDescriptors": {
+ "Schema Branch": {
+ "name": "Schema Branch",
+ "displayName": "Schema Branch",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Reference Reader": {
+ "name": "Schema Reference Reader",
+ "displayName": "Schema Reference Reader",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Text": {
+ "name": "Schema Text",
+ "displayName": "Schema Text",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Allow Scientific Notation": {
+ "name": "Allow Scientific Notation",
+ "displayName": "Allow Scientific Notation",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "compression-level": {
+ "name": "compression-level",
+ "displayName": "Compression Level",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Registry": {
+ "name": "Schema Registry",
+ "displayName": "Schema Registry",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Access Strategy": {
+ "name": "Schema Access Strategy",
+ "displayName": "Schema Access Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Name": {
+ "name": "Schema Name",
+ "displayName": "Schema Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Timestamp Format": {
+ "name": "Timestamp Format",
+ "displayName": "Timestamp Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Date Format": {
+ "name": "Date Format",
+ "displayName": "Date Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Pretty Print JSON": {
+ "name": "Pretty Print JSON",
+ "displayName": "Pretty Print JSON",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "compression-format": {
+ "name": "compression-format",
+ "displayName": "Compression Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Write Strategy": {
+ "name": "Schema Write Strategy",
+ "displayName": "Schema Write Strategy",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "suppress-nulls": {
+ "name": "suppress-nulls",
+ "displayName": "Suppress Null Values",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "output-grouping": {
+ "name": "output-grouping",
+ "displayName": "Output Grouping",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Cache": {
+ "name": "Schema Cache",
+ "displayName": "Schema Cache",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Version": {
+ "name": "Schema Version",
+ "displayName": "Schema Version",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Time Format": {
+ "name": "Time Format",
+ "displayName": "Time Format",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Reference Writer": {
+ "name": "Schema Reference Writer",
+ "displayName": "Schema Reference Writer",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.serialization.RecordSetWriterFactory",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "34d29160-0744-373c-a1c7-39c548e7e6b1",
+ "instanceIdentifier": "e0f7c153-e070-369b-451d-745b53ebe26a",
+ "name": "ConfluentSchemaRegistry",
+ "comments": "",
+ "type": "org.apache.nifi.confluent.schemaregistry.ConfluentSchemaRegistry",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-confluent-platform-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "SSL Context Service": null,
+ "Username": "#{Schema Registry Username}",
+ "Schema Registry URLs": "#{Schema Registry URLs}",
+ "Communications Timeout": "30 secs",
+ "Cache Size": "1000",
+ "Authentication Type": "#{Schema Registry Authentication Type}",
+ "Cache Expiration": "1 hour",
+ "Password": "#{Schema Registry Password}"
+ },
+ "propertyDescriptors": {
+ "SSL Context Service": {
+ "name": "SSL Context Service",
+ "displayName": "SSL Context Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Username": {
+ "name": "Username",
+ "displayName": "Username",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Schema Registry URLs": {
+ "name": "Schema Registry URLs",
+ "displayName": "Schema Registry URLs",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Communications Timeout": {
+ "name": "Communications Timeout",
+ "displayName": "Communications Timeout",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Cache Size": {
+ "name": "Cache Size",
+ "displayName": "Cache Size",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Authentication Type": {
+ "name": "Authentication Type",
+ "displayName": "Authentication Type",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Cache Expiration": {
+ "name": "Cache Expiration",
+ "displayName": "Cache Expiration",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "Password": {
+ "name": "Password",
+ "displayName": "Password",
+ "identifiesControllerService": false,
+ "sensitive": true,
+ "dynamic": false
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.schemaregistry.services.SchemaRegistry",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "ecba6fe5-6beb-3b68-bad9-163a3f14c4c8",
+ "instanceIdentifier": "da19419b-4821-315c-74b2-03d97cf411dc",
+ "name": "ConfluentEncodedSchemaReferenceReader",
+ "comments": "",
+ "type": "org.apache.nifi.confluent.schemaregistry.ConfluentEncodedSchemaReferenceReader",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-confluent-platform-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {},
+ "propertyDescriptors": {},
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.schemaregistry.services.SchemaReferenceReader",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-standard-services-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ },
+ {
+ "identifier": "ccf048dc-004a-35be-8e4d-c7a763a30b02",
+ "instanceIdentifier": "5549d8f6-48eb-3677-821e-292b9b7e73d2",
+ "name": "Kafka3ConnectionService",
+ "comments": "",
+ "type": "org.apache.nifi.kafka.service.Kafka3ConnectionService",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-kafka-3-service-nar",
+ "version": "2.7.0-SNAPSHOT"
+ },
+ "properties": {
+ "ack.wait.time": "10 sec",
+ "sasl.username": "#{Kafka SASL Username}",
+ "max.poll.records": "20000",
+ "bootstrap.servers": "#{Kafka Bootstrap Servers}",
+ "sasl.kerberos.service.name": null,
+ "security.protocol": "#{Kafka Security Protocol}",
+ "SSL Context Service": null,
+ "sasl.mechanism": "#{Kafka SASL Mechanism}",
+ "isolation.level": "read_committed",
+ "oauth2-access-token-provider-service": null,
+ "max.block.ms": "10 sec",
+ "kerberos-user-service": null,
+ "sasl.password": "#{Kafka SASL Password}",
+ "default.api.timeout.ms": "60 sec"
+ },
+ "propertyDescriptors": {
+ "ack.wait.time": {
+ "name": "ack.wait.time",
+ "displayName": "Acknowledgment Wait Time",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "sasl.username": {
+ "name": "sasl.username",
+ "displayName": "SASL Username",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "max.poll.records": {
+ "name": "max.poll.records",
+ "displayName": "Max Poll Records",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "bootstrap.servers": {
+ "name": "bootstrap.servers",
+ "displayName": "Bootstrap Servers",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "sasl.kerberos.service.name": {
+ "name": "sasl.kerberos.service.name",
+ "displayName": "Kerberos Service Name",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "security.protocol": {
+ "name": "security.protocol",
+ "displayName": "Security Protocol",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "SSL Context Service": {
+ "name": "SSL Context Service",
+ "displayName": "SSL Context Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "sasl.mechanism": {
+ "name": "sasl.mechanism",
+ "displayName": "SASL Mechanism",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "isolation.level": {
+ "name": "isolation.level",
+ "displayName": "Transaction Isolation Level",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "oauth2-access-token-provider-service": {
+ "name": "oauth2-access-token-provider-service",
+ "displayName": "OAuth2 Access Token Provider Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "max.block.ms": {
+ "name": "max.block.ms",
+ "displayName": "Max Metadata Wait Time",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "kerberos-user-service": {
+ "name": "kerberos-user-service",
+ "displayName": "Kerberos User Service",
+ "identifiesControllerService": true,
+ "sensitive": false,
+ "dynamic": false
+ },
+ "sasl.password": {
+ "name": "sasl.password",
+ "displayName": "SASL Password",
+ "identifiesControllerService": false,
+ "sensitive": true,
+ "dynamic": false
+ },
+ "default.api.timeout.ms": {
+ "name": "default.api.timeout.ms",
+ "displayName": "Client Timeout",
+ "identifiesControllerService": false,
+ "sensitive": false,
+ "dynamic": false
+ }
+ },
+ "controllerServiceApis": [
+ {
+ "type": "org.apache.nifi.kafka.service.api.KafkaConnectionService",
+ "bundle": {
+ "group": "org.apache.nifi",
+ "artifact": "nifi-kafka-service-api-nar",
+ "version": "2.7.0-SNAPSHOT"
+ }
+ }
+ ],
+ "scheduledState": "DISABLED",
+ "bulletinLevel": "WARN",
+ "componentType": "CONTROLLER_SERVICE",
+ "groupIdentifier": "b3a5c0de-2130-3ba9-942b-9bae5735bd4b"
+ }
+ ],
+ "parameterContextName": "Kafka to S3",
+ "defaultFlowFileExpiration": "0 sec",
+ "defaultBackPressureObjectThreshold": 10000,
+ "defaultBackPressureDataSizeThreshold": "1 GB",
+ "scheduledState": "ENABLED",
+ "executionEngine": "INHERITED",
+ "maxConcurrentTasks": 1,
+ "statelessFlowTimeout": "1 min",
+ "componentType": "PROCESS_GROUP",
+ "flowFileConcurrency": "UNBOUNDED",
+ "flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE"
+ },
+ "externalControllerServices": {},
+ "parameterContexts": {
+ "Kafka to S3": {
+ "name": "Kafka to S3",
+ "parameters": [
+ {
+ "name": "S3 Secret Access Key",
+ "description": "",
+ "sensitive": true,
+ "provided": false
+ },
+ {
+ "name": "Schema Registry Password",
+ "description": "",
+ "sensitive": true,
+ "provided": false
+ },
+ {
+ "name": "Consumer Group ID",
+ "description": "",
+ "sensitive": false,
+ "provided": false
+ },
+ {
+ "name": "Kafka SASL Mechanism",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "SCRAM-SHA-512"
+ },
+ {
+ "name": "Kafka Data Format",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "JSON"
+ },
+ {
+ "name": "Schema Registry Username",
+ "description": "",
+ "sensitive": false,
+ "provided": false
+ },
+ {
+ "name": "S3 Data Format",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "JSON"
+ },
+ {
+ "name": "Schema Registry URLs",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "http://localhost:8081"
+ },
+ {
+ "name": "Merge Latency",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "10 mins"
+ },
+ {
+ "name": "S3 Endpoint Override URL",
+ "description": "",
+ "sensitive": false,
+ "provided": false
+ },
+ {
+ "name": "Schema Registry Authentication Type",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "BASIC"
+ },
+ {
+ "name": "Kafka Security Protocol",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "SASL_SSL"
+ },
+ {
+ "name": "Maximum Object size",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "300 MB"
+ },
+ {
+ "name": "S3 Region",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "us-west-2"
+ },
+ {
+ "name": "Kafka SASL Password",
+ "description": "",
+ "sensitive": true,
+ "provided": false
+ },
+ {
+ "name": "Kafka Bootstrap Servers",
+ "description": "",
+ "sensitive": false,
+ "provided": false
+ },
+ {
+ "name": "Kafka Auto Offset Reset",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "latest"
+ },
+ {
+ "name": "S3 Bucket",
+ "description": "",
+ "sensitive": false,
+ "provided": false
+ },
+ {
+ "name": "Kafka SASL Username",
+ "description": "",
+ "sensitive": false,
+ "provided": false
+ },
+ {
+ "name": "S3 Access Key ID",
+ "description": "",
+ "sensitive": true,
+ "provided": false
+ },
+ {
+ "name": "Target Object Size",
+ "description": "",
+ "sensitive": false,
+ "provided": false,
+ "value": "256 MB"
+ },
+ {
+ "name": "Topic Names",
+ "description": "",
+ "sensitive": false,
+ "provided": false
+ },
+ {
+ "name": "S3 Prefix",
+ "description": "",
+ "sensitive": false,
+ "provided": false
+ }
+ ],
+ "inheritedParameterContexts": [],
+ "description": "",
+ "componentType": "PARAMETER_CONTEXT"
+ }
+ },
+ "flowEncodingVersion": "1.0",
+ "parameterProviders": {},
+ "latest": false
+}
\ No newline at end of file
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml
new file mode 100644
index 0000000000..c36971572c
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/pom.xml
@@ -0,0 +1,226 @@
+<?xml version="1.0"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-to-s3-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-kafka-to-s3-integration-tests</artifactId>
+
+ <repositories>
+ <repository>
+ <id>confluent</id>
+ <url>https://packages.confluent.io/maven/</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-api</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Test Containers for Kafka and S3 -->
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers-kafka</artifactId>
+ <version>2.0.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>localstack</artifactId>
+ <version>1.20.4</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Kafka client for producing test data -->
+ <dependency>
+ <groupId>org.apache.kafka</groupId>
+ <artifactId>kafka-clients</artifactId>
+ <version>3.9.0</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Confluent Schema Registry and Avro dependencies for testing -->
+ <dependency>
+ <groupId>io.confluent</groupId>
+ <artifactId>kafka-avro-serializer</artifactId>
+ <version>7.8.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.confluent</groupId>
+ <artifactId>kafka-schema-registry-client</artifactId>
+ <version>7.8.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.avro</groupId>
+ <artifactId>avro</artifactId>
+ <version>1.11.3</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- AWS SDK for S3 verification in tests -->
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>s3</artifactId>
+ <version>2.36.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>software.amazon.awssdk</groupId>
+ <artifactId>url-connection-client</artifactId>
+ <version>2.36.0</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Necessary NAR dependencies -->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-to-s3-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-3-service-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-service-api-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-aws-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-aws-service-api-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-standard-services-api-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-standard-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-standard-shared-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-record-serialization-services-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-confluent-platform-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+
+ <!-- NARs required by framework -->
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-framework-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-jetty-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-provenance-repository-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-mock-server-nar</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ <type>nar</type>
+ </dependency>
+ </dependencies>
+
+ <!-- Copy nars into libDir for test harness -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-nar</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/libDir</outputDirectory>
+ <includeTypes>nar</includeTypes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java
new file mode 100644
index 0000000000..77b748c74e
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-integration-tests/src/test/java/org/apache/nifi/connectors/kafkas3/KafkaToS3IT.java
@@ -0,0 +1,742 @@
+/*
+ * 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.connectors.kafkas3;
+
+import io.confluent.kafka.serializers.KafkaAvroSerializer;
+import org.apache.avro.Schema;
+import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericRecord;
+import org.apache.kafka.clients.CommonClientConfigs;
+import org.apache.kafka.clients.admin.AdminClient;
+import org.apache.kafka.clients.admin.AdminClientConfig;
+import org.apache.kafka.clients.admin.NewTopic;
+import org.apache.kafka.clients.producer.KafkaProducer;
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.apache.kafka.common.config.SaslConfigs;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.FlowUpdateException;
+import org.apache.nifi.components.connector.PropertyGroupConfiguration;
+import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
+import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
+import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.localstack.LocalStackContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.kafka.ConfluentKafkaContainer;
+import org.testcontainers.utility.DockerImageName;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.ResponseInputStream;
+import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
+import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
+import software.amazon.awssdk.services.s3.model.S3Object;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ExecutionException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class KafkaToS3IT {
+
+ private static ConnectorTestRunner runner;
+ private static Network network;
+ private static ConfluentKafkaContainer kafkaContainer;
+ private static GenericContainer<?> schemaRegistryContainer;
+ private static LocalStackContainer localStackContainer;
+ private static S3Client s3Client;
+
+ private static final String SCRAM_USERNAME = "testuser";
+ private static final String SCRAM_PASSWORD = "testpassword";
+
+ private static final String S3_REGION = "us-west-2";
+
+ // JAAS configuration for Kafka broker SASL/PLAIN authentication.
+ // The 'username' and 'password' fields are credentials the broker uses for inter-broker communication.
+ // The 'user_<username>="<password>"' entries define client users that can authenticate to this broker.
+ // In this setup:
+ // - Broker uses 'admin' / 'admin-secret' for inter-broker communication (though we use PLAINTEXT for that)
+ // - Clients can authenticate using 'testuser' / 'testpassword' on the SASL listener with PLAIN mechanism
+ private static final String JAAS_CONFIG_CONTENT = """
+ KafkaServer {
+ org.apache.kafka.common.security.plain.PlainLoginModule required
+ username="admin"
+ password="admin-secret"
+ user_%s="%s";
+ };
+ """.formatted(SCRAM_USERNAME, SCRAM_PASSWORD);
+
+
+ @BeforeAll
+ public static void setupTestContainers() {
+ network = Network.newNetwork();
+
+ kafkaContainer = new ConfluentKafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.8.0"))
+ .withNetwork(network)
+ .withNetworkAliases("kafka")
+ .withStartupTimeout(Duration.ofSeconds(10))
+ .withEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "CONTROLLER:PLAINTEXT,BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SASL:SASL_PLAINTEXT")
+ .withEnv("KAFKA_LISTENERS", "CONTROLLER://0.0.0.0:9094,BROKER://0.0.0.0:9092,PLAINTEXT://0.0.0.0:19092,SASL://0.0.0.0:9093")
+ .withEnv("KAFKA_ADVERTISED_LISTENERS", "BROKER://kafka:9092,PLAINTEXT://kafka:19092,SASL://localhost:9093")
+ .withEnv("KAFKA_CONTROLLER_LISTENER_NAMES", "CONTROLLER")
+ .withEnv("KAFKA_INTER_BROKER_LISTENER_NAME", "BROKER")
+ .withEnv("KAFKA_SASL_ENABLED_MECHANISMS", "PLAIN")
+ .withEnv("KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS", "0")
+ .withEnv("KAFKA_GROUP_MIN_SESSION_TIMEOUT_MS", "1000")
+ .withEnv("KAFKA_GROUP_MAX_SESSION_TIMEOUT_MS", "60000")
+ .withEnv("KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR", "1")
+ .withEnv("KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR", "1")
+ .withEnv("KAFKA_TRANSACTION_STATE_LOG_MIN_ISR", "1")
+ .withEnv("KAFKA_OPTS", "-Djava.security.auth.login.config=/tmp/kafka_jaas.conf")
+ .withCommand(
+ "sh", "-c",
+ "echo '" + JAAS_CONFIG_CONTENT + "' > /tmp/kafka_jaas.conf && " +
+ "/etc/confluent/docker/run"
+ );
+
+ kafkaContainer.setPortBindings(List.of("9093:9093"));
+ kafkaContainer.start();
+
+ schemaRegistryContainer = new GenericContainer<>(DockerImageName.parse("confluentinc/cp-schema-registry:7.8.0"))
+ .withNetwork(network)
+ .withExposedPorts(8081)
+ .withEnv("SCHEMA_REGISTRY_HOST_NAME", "schema-registry")
+ .withEnv("SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS", "PLAINTEXT://kafka:19092")
+ .withEnv("SCHEMA_REGISTRY_LISTENERS", "http://0.0.0.0:8081")
+ .withStartupTimeout(Duration.ofSeconds(60))
+ .waitingFor(Wait.forHttp("/subjects").forStatusCode(200))
+ .dependsOn(kafkaContainer);
+
+ schemaRegistryContainer.start();
+
+ localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:4.1.0"))
+ .withServices(LocalStackContainer.Service.S3)
+ .withStartupTimeout(Duration.ofSeconds(30));
+
+ localStackContainer.start();
+
+ s3Client = S3Client.builder()
+ .endpointOverride(localStackContainer.getEndpoint())
+ .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(localStackContainer.getAccessKey(), localStackContainer.getSecretKey())))
+ .region(Region.of(S3_REGION))
+ .httpClient(UrlConnectionHttpClient.builder().build())
+ .forcePathStyle(true)
+ .build();
+ }
+
+ @BeforeEach
+ public void setupRunner() {
+ runner = new StandardConnectorTestRunner.Builder()
+ .connectorClassName("org.apache.nifi.connectors.kafkas3.KafkaToS3")
+ .narLibraryDirectory(new File("target/libDir"))
+ .build();
+ assertNotNull(runner);
+ }
+
+ @AfterAll
+ public static void cleanupTestContainers() {
+ if (s3Client != null) {
+ s3Client.close();
+ }
+
+ if (localStackContainer != null) {
+ localStackContainer.stop();
+ }
+
+ if (schemaRegistryContainer != null) {
+ schemaRegistryContainer.stop();
+ }
+
+ if (kafkaContainer != null) {
+ kafkaContainer.stop();
+ }
+
+ if (network != null) {
+ network.close();
+ }
+ }
+
+ @AfterEach
+ public void cleanupRunner() throws IOException {
+ if (runner != null) {
+ runner.close();
+ }
+ }
+
+
+ private void createS3Bucket(final String bucketName) {
+ s3Client.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());
+ }
+
+ private void createKafkaTopics(final String... topicNames) throws ExecutionException, InterruptedException {
+ final Properties adminProps = new Properties();
+ adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9093");
+ adminProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_PLAINTEXT");
+ adminProps.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
+ adminProps.put(SaslConfigs.SASL_JAAS_CONFIG, String.format(
+ "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
+ SCRAM_USERNAME, SCRAM_PASSWORD
+ ));
+
+ try (final AdminClient adminClient = AdminClient.create(adminProps)) {
+ final List<NewTopic> topics = new ArrayList<>();
+ for (final String topicName : topicNames) {
+ topics.add(new NewTopic(topicName, 1, (short) 1));
+ }
+
+ adminClient.createTopics(topics).all().get();
+ }
+ }
+
+ private void produceRecordsToTopic(final String topicName, final String... records) throws ExecutionException, InterruptedException {
+ final Properties producerProps = new Properties();
+ producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9093");
+ producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
+ producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
+ producerProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_PLAINTEXT");
+ producerProps.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
+ producerProps.put(SaslConfigs.SASL_JAAS_CONFIG, String.format(
+ "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
+ SCRAM_USERNAME, SCRAM_PASSWORD
+ ));
+
+ try (final KafkaProducer<String, String> producer = new KafkaProducer<>(producerProps)) {
+ for (final String record : records) {
+ final ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topicName, record);
+ producer.send(producerRecord).get();
+ }
+
+ producer.flush();
+ }
+ }
+
+ private String getSchemaRegistryUrl() {
+ return String.format("http://%s:%d", schemaRegistryContainer.getHost(), schemaRegistryContainer.getMappedPort(8081));
+ }
+
+ private void produceAvroRecordsToTopic(final String topicName, final Schema schema, final GenericRecord... records) throws ExecutionException, InterruptedException {
+ final Properties producerProps = new Properties();
+ producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9093");
+ producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
+ producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
+ producerProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_PLAINTEXT");
+ producerProps.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
+ producerProps.put(SaslConfigs.SASL_JAAS_CONFIG, String.format(
+ "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
+ SCRAM_USERNAME, SCRAM_PASSWORD
+ ));
+ producerProps.put("schema.registry.url", getSchemaRegistryUrl());
+
+ try (final KafkaProducer<String, GenericRecord> producer = new KafkaProducer<>(producerProps)) {
+ for (final GenericRecord record : records) {
+ final ProducerRecord<String, GenericRecord> producerRecord = new ProducerRecord<>(topicName, record);
+ producer.send(producerRecord).get();
+ }
+
+ producer.flush();
+ }
+ }
+
+
+ @Test
+ public void testVerification() throws ExecutionException, InterruptedException, FlowUpdateException {
+ createKafkaTopics("topic-1", "topic-2", "topic-3", "topic-4", "topic-5", "Z-topic", "an-important-topic");
+
+ produceRecordsToTopic("topic-1",
+ """
+ {"id": 1, "name": "Alice", "age": 30}""",
+ """
+ {"id": 2, "name": "Bob", "age": 25}""",
+ """
+ {"id": 3, "name": "Charlie", "age": 35}"""
+ );
+
+ produceRecordsToTopic("an-important-topic",
+ "This is a plaintext message",
+ "Another important message",
+ "Final plaintext record"
+ );
+
+ final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ "Kafka Brokers", "localhost:9093",
+ "Security Protocol", "SASL_PLAINTEXT",
+ "SASL Mechanism", "PLAIN",
+ "Username", SCRAM_USERNAME,
+ "Password", SCRAM_PASSWORD
+ ));
+
+ runner.applyUpdate();
+
+ // Perform verification to ensure that valid server configuration passes
+ final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", List.of(kafkaServerConfig));
+ connectionVerificationResults.assertNoFailures();
+
+ // Apply the configuration that we've now validated
+ runner.configure("Kafka Connection", List.of(kafkaServerConfig));
+
+ // Perform verification to ensure that valid topic configuration passes
+ final PropertyGroupConfiguration topic1Config = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ "Topic Names", "topic-1",
+ "Consumer Group ID", "nifi-kafka-to-s3-testSuccessfulFlow",
+ "Offset Reset", "earliest",
+ "Kafka Data Format", "JSON"
+ ));
+ final ConnectorConfigVerificationResult topic1VerificationResults = runner.verifyConfiguration("Kafka Topics", List.of(topic1Config));
+ topic1VerificationResults.assertNoFailures();
+
+ // Perform verification against a topic with invalid data for the selected data format
+ final PropertyGroupConfiguration importantTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ "Topic Names", "an-important-topic",
+ "Consumer Group ID", "nifi-kafka-to-s3-testSuccessfulFlow",
+ "Offset Reset", "earliest",
+ "Kafka Data Format", "JSON"
+ ));
+
+ final ConnectorConfigVerificationResult importantTopicVerificationResults = runner.verifyConfiguration("Kafka Topics", List.of(importantTopicConfig));
+ final List<ConfigVerificationResult> invalidImportantTopicResults = importantTopicVerificationResults.getFailedResults();
+ assertEquals(1, invalidImportantTopicResults.size());
+ final ConfigVerificationResult invalidResult = invalidImportantTopicResults.getFirst();
+ assertTrue(invalidResult.getExplanation().contains("parse"), "Unexpected validation reason: " + invalidResult.getExplanation());
+
+ runner.applyUpdate();
+ }
+
+ @Test
+ public void testJsonFlow() throws IOException, ExecutionException, InterruptedException, FlowUpdateException {
+ final String bucketName = "test-json-flow";
+ createS3Bucket(bucketName);
+ createKafkaTopics("story");
+
+ produceRecordsToTopic("story",
+ """
+ {"page": 1, "words": "Once upon a time, there was a NiFi developer." }""",
+ """
+ {"page": 2, "words": "The developer wanted to build a connector to move data from Kafka to S3." }""",
+ """
+ {"page": 3, "words": "After much effort, the connector was complete and worked flawlessly!" }""",
+ """
+ {"page": 4, "words": "The end." }"""
+ );
+
+ final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ "Kafka Brokers", "localhost:9093",
+ "Security Protocol", "SASL_PLAINTEXT",
+ "SASL Mechanism", "PLAIN",
+ "Username", SCRAM_USERNAME,
+ "Password", SCRAM_PASSWORD
+ ));
+
+ final PropertyGroupConfiguration kafkaTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ "Topic Names", "story",
+ "Consumer Group ID", "nifi-kafka-to-s3-testSuccessfulFlow",
+ "Offset Reset", "earliest",
+ "Kafka Data Format", "JSON"
+ ));
+
+ final PropertyGroupConfiguration s3DestinationConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
+ "S3 Region", S3_REGION,
+ "S3 Data Format", "Avro",
+ "S3 Bucket", bucketName,
+ "S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()
+ ));
+ final PropertyGroupConfiguration s3CredentialsConfig = PropertyGroupConfiguration.fromStringValues("S3 Credentials", Map.of(
+ "S3 Authentication Strategy", "Access Key ID and Secret Key",
+ "S3 Access Key ID", localStackContainer.getAccessKey(),
+ "S3 Secret Access Key", localStackContainer.getSecretKey()
+ ));
+ final PropertyGroupConfiguration s3MergeConfig = PropertyGroupConfiguration.fromStringValues("Merge Configuration", Map.of(
+ "Target Object Size", "1 MB",
+ "Merge Latency", "1 sec"
+ ));
+
+ runner.configure("Kafka Connection", List.of(kafkaServerConfig));
+ runner.configure("Kafka Topics", List.of(kafkaTopicConfig));
+ runner.configure("S3 Configuration", List.of(s3DestinationConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.applyUpdate();
+
+ final List<ValidationResult> validationResults = runner.validate();
+ assertEquals(Collections.emptyList(), validationResults);
+
+ runner.startConnector();
+ try {
+ runner.waitForDataIngested(Duration.ofSeconds(10));
+ runner.waitForIdle(Duration.ofSeconds(30));
+ } finally {
+ runner.stopConnector();
+ }
+
+ verifyS3ObjectsCreated(bucketName);
+ }
+
+ private void verifyS3ObjectsCreated(final String bucketName) throws IOException {
+ final ListObjectsV2Response listResponse = s3Client.listObjectsV2(ListObjectsV2Request.builder().bucket(bucketName).build());
+ final List<S3Object> objects = listResponse.contents();
+
+ assertFalse(objects.isEmpty(), "Expected at least one object in S3 bucket");
+
+ for (final S3Object s3Object : objects) {
+ final GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucketName).key(s3Object.key()).build();
+ try (final ResponseInputStream<GetObjectResponse> objectContent = s3Client.getObject(getObjectRequest)) {
+ final long objectSize = objectContent.response().contentLength();
+ assertTrue(objectSize > 0, "Expected S3 object " + s3Object.key() + " to have content");
+ }
+ }
+ }
+
+ @Test
+ public void testSchemaRegistryVerification() throws ExecutionException, InterruptedException, FlowUpdateException {
+ createKafkaTopics("avro-topic");
+
+ final String schemaString = """
+ {
+ "type": "record",
+ "name": "TestRecord",
+ "namespace": "org.apache.nifi.test",
+ "fields": [
+ {"name": "id", "type": "int"},
+ {"name": "message", "type": "string"}
+ ]
+ }""";
+
+ final Schema schema = new Schema.Parser().parse(schemaString);
+
+ final GenericRecord record1 = new GenericData.Record(schema);
+ record1.put("id", 100);
+ record1.put("message", "Test message 1");
+
+ final GenericRecord record2 = new GenericData.Record(schema);
+ record2.put("id", 200);
+ record2.put("message", "Test message 2");
+
+ produceAvroRecordsToTopic("avro-topic", schema, record1, record2);
+
+ final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ "Kafka Brokers", "localhost:9093",
+ "Security Protocol", "SASL_PLAINTEXT",
+ "SASL Mechanism", "PLAIN",
+ "Username", SCRAM_USERNAME,
+ "Password", SCRAM_PASSWORD
+ ));
+
+ final PropertyGroupConfiguration schemaRegistryConfig = PropertyGroupConfiguration.fromStringValues("Schema Registry Settings", Map.of(
+ "Schema Registry URL", getSchemaRegistryUrl()
+ ));
+
+ final ConnectorConfigVerificationResult connectionVerificationResults = runner.verifyConfiguration("Kafka Connection", List.of(kafkaServerConfig, schemaRegistryConfig));
+ connectionVerificationResults.assertNoFailures();
+
+ runner.configure("Kafka Connection", List.of(kafkaServerConfig, schemaRegistryConfig));
+
+ final PropertyGroupConfiguration avroTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ "Topic Names", "avro-topic",
+ "Consumer Group ID", "nifi-kafka-to-s3-testSchemaRegistryVerification",
+ "Offset Reset", "earliest",
+ "Kafka Data Format", "Avro"
+ ));
+
+ final ConnectorConfigVerificationResult avroTopicVerificationResults = runner.verifyConfiguration("Kafka Topics", List.of(avroTopicConfig));
+ avroTopicVerificationResults.assertNoFailures();
+
+ runner.applyUpdate();
+ }
+
+ @Test
+ public void testWithSchemaRegistry() throws IOException, ExecutionException, InterruptedException, FlowUpdateException {
+ final String bucketName = "test-schema-registry";
+ createS3Bucket(bucketName);
+ createKafkaTopics("user-events");
+
+ final String schemaString = """
+ {
+ "type": "record",
+ "name": "UserEvent",
+ "namespace": "org.apache.nifi.test",
+ "fields": [
+ {"name": "userId", "type": "int"},
+ {"name": "userName", "type": "string"},
+ {"name": "eventType", "type": "string"},
+ {"name": "timestamp", "type": "long"}
+ ]
+ }""";
+
+ final Schema schema = new Schema.Parser().parse(schemaString);
+
+ final GenericRecord record1 = new GenericData.Record(schema);
+ record1.put("userId", 1001);
+ record1.put("userName", "alice");
+ record1.put("eventType", "login");
+ record1.put("timestamp", System.currentTimeMillis());
+
+ final GenericRecord record2 = new GenericData.Record(schema);
+ record2.put("userId", 1002);
+ record2.put("userName", "bob");
+ record2.put("eventType", "purchase");
+ record2.put("timestamp", System.currentTimeMillis());
+
+ final GenericRecord record3 = new GenericData.Record(schema);
+ record3.put("userId", 1003);
+ record3.put("userName", "charlie");
+ record3.put("eventType", "logout");
+ record3.put("timestamp", System.currentTimeMillis());
+
+ produceAvroRecordsToTopic("user-events", schema, record1, record2, record3);
+
+ final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ "Kafka Brokers", "localhost:9093",
+ "Security Protocol", "SASL_PLAINTEXT",
+ "SASL Mechanism", "PLAIN",
+ "Username", SCRAM_USERNAME,
+ "Password", SCRAM_PASSWORD
+ ));
+
+ final PropertyGroupConfiguration schemaRegistryConfig = PropertyGroupConfiguration.fromStringValues("Schema Registry Settings", Map.of(
+ "Schema Registry URL", getSchemaRegistryUrl()
+ ));
+
+ final PropertyGroupConfiguration kafkaTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ "Topic Names", "user-events",
+ "Consumer Group ID", "nifi-kafka-to-s3-testSchemaRegistry",
+ "Offset Reset", "earliest",
+ "Kafka Data Format", "Avro"
+ ));
+
+ final PropertyGroupConfiguration s3DestinationConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
+ "S3 Region", S3_REGION,
+ "S3 Data Format", "Avro",
+ "S3 Bucket", bucketName,
+ "S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()
+ ));
+ final PropertyGroupConfiguration s3CredentialsConfig = PropertyGroupConfiguration.fromStringValues("S3 Credentials", Map.of(
+ "S3 Authentication Strategy", "Access Key ID and Secret Key",
+ "S3 Access Key ID", localStackContainer.getAccessKey(),
+ "S3 Secret Access Key", localStackContainer.getSecretKey()
+ ));
+ final PropertyGroupConfiguration s3MergeConfig = PropertyGroupConfiguration.fromStringValues("Merge Configuration", Map.of(
+ "Target Object Size", "1 MB",
+ "Merge Latency", "1 sec"
+ ));
+
+ runner.configure("Kafka Connection", List.of(kafkaServerConfig, schemaRegistryConfig));
+ runner.configure("Kafka Topics", List.of(kafkaTopicConfig));
+ runner.configure("S3 Configuration", List.of(s3DestinationConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.applyUpdate();
+
+ final List<ValidationResult> validationResults = runner.validate();
+ assertEquals(Collections.emptyList(), validationResults);
+
+ runner.startConnector();
+ try {
+ runner.waitForDataIngested(Duration.ofSeconds(10));
+ runner.waitForIdle(Duration.ofSeconds(30));
+ } finally {
+ runner.stopConnector();
+ }
+
+ verifyS3ObjectsCreated(bucketName);
+ }
+
+ @Test
+ public void testSwitchFromJsonToAvroS3Format() throws IOException, ExecutionException, InterruptedException, FlowUpdateException {
+ final String bucketName = "test-switch-formats";
+ createS3Bucket(bucketName);
+
+ // Add data to json and avro topics
+ createKafkaTopics("json", "avro");
+
+ produceRecordsToTopic("json",
+ """
+ {"id": 1, "type": "json", "data": "First JSON record"}""",
+ """
+ {"id": 2, "type": "json", "data": "Second JSON record"}""",
+ """
+ {"id": 3, "type": "json", "data": "Third JSON record"}"""
+ );
+
+ final String schemaString = """
+ {
+ "type": "record",
+ "name": "AvroRecord",
+ "namespace": "org.apache.nifi.test",
+ "fields": [
+ {"name": "id", "type": "int"},
+ {"name": "type", "type": "string"},
+ {"name": "data", "type": "string"}
+ ]
+ }""";
+
+ final Schema schema = new Schema.Parser().parse(schemaString);
+
+ final GenericRecord avroRecord1 = new GenericData.Record(schema);
+ avroRecord1.put("id", 10);
+ avroRecord1.put("type", "avro");
+ avroRecord1.put("data", "First Avro record");
+
+ final GenericRecord avroRecord2 = new GenericData.Record(schema);
+ avroRecord2.put("id", 20);
+ avroRecord2.put("type", "avro");
+ avroRecord2.put("data", "Second Avro record");
+
+ final GenericRecord avroRecord3 = new GenericData.Record(schema);
+ avroRecord3.put("id", 30);
+ avroRecord3.put("type", "avro");
+ avroRecord3.put("data", "Third Avro record");
+
+ produceAvroRecordsToTopic("avro", schema, avroRecord1, avroRecord2, avroRecord3);
+
+ // Configure Connector to consume from JSON Kafka topic and write to S3 in JSON format, but with an invalid S3 endpoint.
+ // This will cause the data to remain queued, since PutS3Object will fail to write the data.
+ final PropertyGroupConfiguration kafkaServerConfig = PropertyGroupConfiguration.fromStringValues("Kafka Server Settings", Map.of(
+ "Kafka Brokers", "localhost:9093",
+ "Security Protocol", "SASL_PLAINTEXT",
+ "SASL Mechanism", "PLAIN",
+ "Username", SCRAM_USERNAME,
+ "Password", SCRAM_PASSWORD
+ ));
+
+ final PropertyGroupConfiguration schemaRegistryConfig = PropertyGroupConfiguration.fromStringValues("Schema Registry Settings", Map.of(
+ "Schema Registry URL", getSchemaRegistryUrl()
+ ));
+
+ final PropertyGroupConfiguration jsonTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ "Topic Names", "json",
+ "Consumer Group ID", "nifi-kafka-to-s3-testReconfiguration",
+ "Offset Reset", "earliest",
+ "Kafka Data Format", "JSON"
+ ));
+
+ final PropertyGroupConfiguration s3InvalidDestinationConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
+ "S3 Region", S3_REGION,
+ "S3 Data Format", "JSON",
+ "S3 Bucket", bucketName,
+ "S3 Endpoint Override URL", "http://invalid-s3-endpoint:9999"
+ ));
+ final PropertyGroupConfiguration s3CredentialsConfig = PropertyGroupConfiguration.fromStringValues("S3 Credentials", Map.of(
+ "S3 Authentication Strategy", "Access Key ID and Secret Key",
+ "S3 Access Key ID", localStackContainer.getAccessKey(),
+ "S3 Secret Access Key", localStackContainer.getSecretKey()
+ ));
+ final PropertyGroupConfiguration s3MergeConfig = PropertyGroupConfiguration.fromStringValues("Merge Configuration", Map.of(
+ "Target Object Size", "1 MB",
+ "Merge Latency", "1 sec"
+ ));
+
+ runner.configure("Kafka Connection", List.of(kafkaServerConfig));
+ runner.configure("Kafka Topics", List.of(jsonTopicConfig));
+ runner.configure("S3 Configuration", List.of(s3InvalidDestinationConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.applyUpdate();
+
+ // Run the Connector with the invalid S3 endpoint to queue the JSON data. Wait for data to be queued up.
+ runner.startConnector();
+ try {
+ runner.waitForDataIngested(Duration.ofSeconds(30));
+ } finally {
+ runner.stopConnector();
+ }
+
+ // Apply configuration to specify the correct S3 endpoint. Keep S3 Data Format as JSON for now.
+ // This will allow the Connector to write data to S3 and properly drain the data when we switch the S3 format from JSON to Avro.
+ final PropertyGroupConfiguration s3ValidDestinationJsonConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
+ "S3 Region", S3_REGION,
+ "S3 Data Format", "JSON",
+ "S3 Bucket", bucketName,
+ "S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()
+ ));
+
+ runner.configure("S3 Configuration", List.of(s3ValidDestinationJsonConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.applyUpdate();
+
+ // Make sure there is no data in S3 yet.
+ final ListObjectsV2Response initialListingResponse = s3Client.listObjectsV2(ListObjectsV2Request.builder().bucket(bucketName).build());
+ final List<S3Object> initialS3Objects = initialListingResponse.contents();
+ assertEquals(List.of(), initialS3Objects);
+
+ // Now change the S3 Data Format from JSON to Avro. This should trigger draining of the queued JSON data.
+ final PropertyGroupConfiguration s3ValidDestinationAvroConfig = PropertyGroupConfiguration.fromStringValues("S3 Destination Configuration", Map.of(
+ "S3 Region", S3_REGION,
+ "S3 Data Format", "Avro",
+ "S3 Bucket", bucketName,
+ "S3 Endpoint Override URL", localStackContainer.getEndpoint().toString()
+ ));
+
+ // Configure to consume from the Avro topic and change S3 format to Avro
+ final PropertyGroupConfiguration avroTopicConfig = PropertyGroupConfiguration.fromStringValues("Kafka Topics Configuration", Map.of(
+ "Topic Names", "avro",
+ "Consumer Group ID", "nifi-kafka-to-s3-testReconfiguration",
+ "Offset Reset", "earliest",
+ "Kafka Data Format", "Avro"
+ ));
+
+ runner.configure("Kafka Connection", List.of(kafkaServerConfig, schemaRegistryConfig));
+ runner.configure("Kafka Topics", List.of(avroTopicConfig));
+ runner.configure("S3 Configuration", List.of(s3ValidDestinationAvroConfig, s3MergeConfig, s3CredentialsConfig));
+ runner.applyUpdate();
+
+ // After draining, there should be one JSON file in S3.
+ final ListObjectsV2Response jsonOnlyListing = s3Client.listObjectsV2(ListObjectsV2Request.builder().bucket(bucketName).build());
+ final List<S3Object> jsonObjects = jsonOnlyListing.contents();
+ assertEquals(1, jsonObjects.size());
+
+ // Start connector, wait for more data to be ingested, and then wait for the Connector to be idle.
+ runner.startConnector();
+ try {
+ runner.waitForDataIngested(Duration.ofSeconds(30));
+ runner.waitForIdle(Duration.ofSeconds(30));
+ } finally {
+ runner.stopConnector();
+ }
+
+ // Verify that there are two objects in S3: one in JSON format and one in Avro format.
+ final ListObjectsV2Response listResponse = s3Client.listObjectsV2(ListObjectsV2Request.builder().bucket(bucketName).build());
+ final List<S3Object> objects = listResponse.contents();
+
+ assertEquals(2, objects.size(), "Expected exactly 2 objects in S3 bucket: one for JSON data and one for Avro data");
+
+ for (final S3Object s3Object : objects) {
+ final GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucketName).key(s3Object.key()).build();
+ try (final ResponseInputStream<GetObjectResponse> objectContent = s3Client.getObject(getObjectRequest)) {
+ final long objectSize = objectContent.response().contentLength();
+ assertTrue(objectSize > 0, "Expected S3 object " + s3Object.key() + " to have content");
+ }
+ }
+ }
+
+}
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/pom.xml
new file mode 100644
index 0000000000..76e6e97097
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/nifi-kafka-to-s3-nar/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-to-s3-bundle</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-kafka-to-s3-nar</artifactId>
+ <packaging>nar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-kafka-to-s3-connector</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml b/nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml
new file mode 100644
index 0000000000..0fbed3c9ea
--- /dev/null
+++ b/nifi-connectors/nifi-kafka-to-s3-bundle/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connectors</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-kafka-to-s3-bundle</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>nifi-kafka-to-s3-connector</module>
+ <module>nifi-kafka-to-s3-nar</module>
+ <module>nifi-kafka-to-s3-integration-tests</module>
+ </modules>
+
+ <dependencyManagement>
+ <dependencies>
+ </dependencies>
+ </dependencyManagement>
+</project>
\ No newline at end of file
diff --git a/nifi-connectors/pom.xml b/nifi-connectors/pom.xml
new file mode 100644
index 0000000000..4186b8904d
--- /dev/null
+++ b/nifi-connectors/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-connectors</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>nifi-kafka-to-s3-bundle</module>
+ </modules>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-connector-utils</artifactId>
+ <version>2.7.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+</project>
\ No newline at end of file
diff --git a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
index 0fd4ebbc7a..8c95637a5e 100644
--- a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
+++ b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
@@ -17,7 +17,9 @@
package org.apache.nifi.processors.aws.s3;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.connector.components.ConnectorMethod;
import org.apache.nifi.context.PropertyContext;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.flowfile.FlowFile;
@@ -26,6 +28,7 @@ import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.processors.aws.AbstractAwsSyncProcessor;
+import org.apache.nifi.processors.aws.region.RegionUtil;
import software.amazon.awssdk.awscore.exception.AwsErrorDetails;
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.services.s3.S3Client;
@@ -34,9 +37,11 @@ import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
import software.amazon.awssdk.services.s3.model.ServerSideEncryption;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import static org.apache.nifi.processors.aws.region.RegionUtil.CUSTOM_REGION;
import static org.apache.nifi.processors.aws.region.RegionUtil.REGION;
@@ -315,4 +320,11 @@ public abstract class AbstractS3Processor extends AbstractAwsSyncProcessor<S3Cli
attributes.put(S3_ENCRYPTION_STRATEGY, encryptionService.getStrategyName());
}
}
+
+ @ConnectorMethod(name="getAvailableRegions", description="Returns the list of available AWS regions")
+ public List<String> getAvailableRegions() {
+ return RegionUtil.getAwsRegionAllowableValues().stream()
+ .map(AllowableValue::getValue)
+ .toList();
+ }
}
diff --git a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-regions/src/main/java/org/apache/nifi/processors/aws/region/RegionUtil.java b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-regions/src/main/java/org/apache/nifi/processors/aws/region/RegionUtil.java
index b5e35d8b3e..2626c41c8a 100644
--- a/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-regions/src/main/java/org/apache/nifi/processors/aws/region/RegionUtil.java
+++ b/nifi-extension-bundles/nifi-aws-bundle/nifi-aws-regions/src/main/java/org/apache/nifi/processors/aws/region/RegionUtil.java
@@ -100,7 +100,7 @@ public final class RegionUtil {
*
* @return All available AWS regions as AllowableValues.
*/
- private static List<AllowableValue> getAwsRegionAllowableValues() {
+ public static List<AllowableValue> getAwsRegionAllowableValues() {
final List<AllowableValue> values = new ArrayList<>();
for (final Region region : Region.regions()) {
values.add(createAwsRegionAllowableValue(region));
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/kafka/processors/ConsumeKafka.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/kafka/processors/ConsumeKafka.java
index 455c5ee5dc..a75a3ae50c 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/kafka/processors/ConsumeKafka.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/main/java/org/apache/nifi/kafka/processors/ConsumeKafka.java
@@ -26,9 +26,11 @@ import org.apache.nifi.annotation.lifecycle.OnScheduled;
import org.apache.nifi.annotation.lifecycle.OnStopped;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.Validator;
+import org.apache.nifi.components.connector.components.ConnectorMethod;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.kafka.processors.common.KafkaUtils;
import org.apache.nifi.kafka.processors.consumer.OffsetTracker;
@@ -58,11 +60,14 @@ import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.VerifiableProcessor;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.serialization.RecordReader;
import org.apache.nifi.serialization.RecordReaderFactory;
import org.apache.nifi.serialization.RecordSetWriterFactory;
import org.apache.nifi.util.StringUtils;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
@@ -512,26 +517,110 @@ public class ConsumeKafka extends AbstractProcessor implements VerifiableProcess
final List<ConfigVerificationResult> verificationResults = new ArrayList<>();
final KafkaConnectionService connectionService = context.getProperty(CONNECTION_SERVICE).asControllerService(KafkaConnectionService.class);
- final PollingContext pollingContext = createPollingContext(context);
- final KafkaConsumerService consumerService = connectionService.getConsumerService(pollingContext);
+ final PollingContext pollingContext = createPollingContext(context, null, AutoOffsetReset.EARLIEST);
+ try (final KafkaConsumerService consumerService = connectionService.getConsumerService(pollingContext)) {
+ final ConfigVerificationResult partitionVerification = verifyPartitions(consumerService, pollingContext);
+ verificationResults.add(partitionVerification);
+
+ final ConfigVerificationResult parsingResult = verifyCanParse(context, consumerService, verificationLogger);
+ verificationResults.add(parsingResult);
+ } catch (final IOException e) {
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Communicate with Kafka Broker")
+ .outcome(Outcome.FAILED)
+ .explanation("There was an I/O failure when communicating with Kafka: " + e)
+ .build());
+ }
+
+ return verificationResults;
+ }
- final ConfigVerificationResult.Builder verificationPartitions = new ConfigVerificationResult.Builder()
- .verificationStepName("Verify Topic Partitions");
+ private ConfigVerificationResult verifyPartitions(final KafkaConsumerService consumerService, final PollingContext pollingContext) {
+ final ConfigVerificationResult.Builder partitionVerification = new ConfigVerificationResult.Builder()
+ .verificationStepName("Verify Topic Partitions");
try {
final List<PartitionState> partitionStates = consumerService.getPartitionStates();
- verificationPartitions
- .outcome(ConfigVerificationResult.Outcome.SUCCESSFUL)
- .explanation(String.format("Partitions [%d] found for Topics %s", partitionStates.size(), pollingContext.getTopics()));
+ partitionVerification
+ .outcome(ConfigVerificationResult.Outcome.SUCCESSFUL)
+ .explanation(String.format("Found [%d] partitions for Topics %s", partitionStates.size(), pollingContext.getTopics()));
} catch (final Exception e) {
getLogger().error("Topics {} Partition verification failed", pollingContext.getTopics(), e);
- verificationPartitions
- .outcome(ConfigVerificationResult.Outcome.FAILED)
- .explanation(String.format("Topics %s Partition access failed: %s", pollingContext.getTopics(), e));
+ partitionVerification
+ .outcome(ConfigVerificationResult.Outcome.FAILED)
+ .explanation(String.format("Topics %s Partition access failed: %s", pollingContext.getTopics(), e));
}
- verificationResults.add(verificationPartitions.build());
- return verificationResults;
+ return partitionVerification.build();
+ }
+
+ private ConfigVerificationResult verifyCanParse(final ProcessContext context, final KafkaConsumerService consumerService, final ComponentLog verificationLogger) {
+ final Iterable<ByteRecord> records = consumerService.poll(Duration.ofSeconds(60));
+ final ProcessingStrategy processingStrategy = context.getProperty(PROCESSING_STRATEGY).asAllowableValue(ProcessingStrategy.class);
+ if (processingStrategy != ProcessingStrategy.RECORD) {
+ return new ConfigVerificationResult.Builder()
+ .verificationStepName("Parse Records")
+ .outcome(Outcome.SKIPPED)
+ .explanation("Processing Strategy is set to " + processingStrategy.getValue() + " so skipping record parsing verification")
+ .build();
+ }
+
+ final RecordReaderFactory readerFactory = context.getProperty(RECORD_READER).asControllerService(RecordReaderFactory.class);
+ int recordIndex = 0;
+ for (final ByteRecord byteRecord : records) {
+ recordIndex++;
+ final Map<String, String> recordAttributes = KafkaUtils.toAttributes(
+ byteRecord, keyEncoding, headerNamePattern, headerEncoding, commitOffsets);
+
+ try (final InputStream inputStream = new ByteArrayInputStream(byteRecord.getValue());
+ final RecordReader reader = readerFactory.createRecordReader(recordAttributes, inputStream, byteRecord.getValue().length, verificationLogger)) {
+
+ while (reader.nextRecord() != null) {
+ }
+ } catch (final Exception e) {
+ return new ConfigVerificationResult.Builder()
+ .verificationStepName("Parse Records")
+ .outcome(Outcome.FAILED)
+ .explanation("Failed to parse Record number " + recordIndex + ": " + e)
+ .build();
+ }
+ }
+
+ if (recordIndex == 0) {
+ return new ConfigVerificationResult.Builder()
+ .verificationStepName("Parse Records")
+ .outcome(Outcome.SKIPPED)
+ .explanation("No records were received to parse")
+ .build();
+ }
+
+ return new ConfigVerificationResult.Builder()
+ .verificationStepName("Parse Records")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Successfully parsed " + recordIndex + " records")
+ .build();
+ }
+
+
+ @ConnectorMethod(
+ name = "sampleTopics",
+ description = "Returns a list of sample data from the topics that would be consumed by this processor."
+ )
+ public List<byte[]> sampleTopics(final ProcessContext context) throws IOException {
+ final KafkaConnectionService connectionService = context.getProperty(CONNECTION_SERVICE).asControllerService(KafkaConnectionService.class);
+ final PollingContext pollingContext = createPollingContext(context, "nifi-validation-" + System.currentTimeMillis(), AutoOffsetReset.EARLIEST);
+ try (final KafkaConsumerService consumerService = connectionService.getConsumerService(pollingContext)) {
+ final Iterable<ByteRecord> records = consumerService.poll(Duration.ofSeconds(60));
+ final List<byte[]> samples = new ArrayList<>();
+ for (final ByteRecord record : records) {
+ samples.add(record.getValue());
+ if (samples.size() >= 10) {
+ break;
+ }
+ }
+
+ return samples;
+ }
}
private KafkaConsumerService getConsumerService(final ProcessContext context) {
@@ -620,6 +709,10 @@ public class ConsumeKafka extends AbstractProcessor implements VerifiableProcess
final String groupId = context.getProperty(GROUP_ID).getValue();
final String offsetReset = context.getProperty(AUTO_OFFSET_RESET).getValue();
final AutoOffsetReset autoOffsetReset = AutoOffsetReset.valueOf(offsetReset.toUpperCase());
+ return createPollingContext(context, groupId, autoOffsetReset);
+ }
+
+ private PollingContext createPollingContext(final ProcessContext context, final String groupId, final AutoOffsetReset autoOffsetReset) {
final String topics = context.getProperty(TOPICS).evaluateAttributeExpressions().getValue();
final String topicFormat = context.getProperty(TOPIC_FORMAT).getValue();
@@ -636,4 +729,5 @@ public class ConsumeKafka extends AbstractProcessor implements VerifiableProcess
return pollingContext;
}
+
}
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java
index 531e5fff07..0fb38dabab 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-processors/src/test/java/org/apache/nifi/kafka/processors/ConsumeKafkaTest.java
@@ -17,6 +17,7 @@
package org.apache.nifi.kafka.processors;
import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.kafka.service.api.KafkaConnectionService;
import org.apache.nifi.kafka.service.api.common.PartitionState;
import org.apache.nifi.kafka.service.api.consumer.KafkaConsumerService;
@@ -31,11 +32,13 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import static org.apache.nifi.kafka.processors.ConsumeKafka.CONNECTION_SERVICE;
import static org.apache.nifi.kafka.processors.ConsumeKafka.GROUP_ID;
import static org.apache.nifi.kafka.processors.ConsumeKafka.TOPICS;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@@ -96,10 +99,15 @@ class ConsumeKafkaTest {
runner.setProperty(GROUP_ID, CONSUMER_GROUP_ID);
final List<ConfigVerificationResult> results = processor.verify(runner.getProcessContext(), runner.getLogger(), Collections.emptyMap());
- assertEquals(1, results.size());
+ final List<ConfigVerificationResult> successResults = results.stream()
+ .filter(result -> result.getOutcome() == ConfigVerificationResult.Outcome.SUCCESSFUL)
+ .toList();
+ assertEquals(1, successResults.size());
- final ConfigVerificationResult firstResult = results.iterator().next();
- assertEquals(ConfigVerificationResult.Outcome.SUCCESSFUL, firstResult.getOutcome());
+ final boolean anyFailures = results.stream().anyMatch(result -> result.getOutcome() == Outcome.FAILED);
+ assertFalse(anyFailures, "At least one verification result was a failure: " + results);
+
+ final ConfigVerificationResult firstResult = successResults.getFirst();
assertNotNull(firstResult.getExplanation());
}
@@ -113,9 +121,14 @@ class ConsumeKafkaTest {
runner.setProperty(GROUP_ID, CONSUMER_GROUP_ID);
final List<ConfigVerificationResult> results = processor.verify(runner.getProcessContext(), runner.getLogger(), Collections.emptyMap());
- assertEquals(1, results.size());
+ assertEquals(2, results.size());
+
+ final List<ConfigVerificationResult> failedResults = results.stream()
+ .filter(result -> result.getOutcome() == ConfigVerificationResult.Outcome.FAILED)
+ .toList();
+ assertEquals(1, failedResults.size());
- final ConfigVerificationResult firstResult = results.iterator().next();
+ final ConfigVerificationResult firstResult = failedResults.getFirst();
assertEquals(ConfigVerificationResult.Outcome.FAILED, firstResult.getOutcome());
assertNotNull(firstResult.getExplanation());
}
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-api/src/main/java/org/apache/nifi/kafka/service/api/consumer/PollingContext.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-api/src/main/java/org/apache/nifi/kafka/service/api/consumer/PollingContext.java
index 27b07b41e3..171ff02fdf 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-api/src/main/java/org/apache/nifi/kafka/service/api/consumer/PollingContext.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-api/src/main/java/org/apache/nifi/kafka/service/api/consumer/PollingContext.java
@@ -34,7 +34,7 @@ public class PollingContext {
public PollingContext(final String groupId, final Collection<String> topics,
final AutoOffsetReset autoOffsetReset) {
- this.groupId = Objects.requireNonNull(groupId, "Group ID required");
+ this.groupId = groupId;
this.topics = Collections.unmodifiableCollection(Objects.requireNonNull(topics, "Topics required"));
this.topicPattern = null;
this.autoOffsetReset = Objects.requireNonNull(autoOffsetReset, "Auto Offset Reset required");
@@ -42,7 +42,7 @@ public class PollingContext {
public PollingContext(final String groupId, final Pattern topicPattern,
final AutoOffsetReset autoOffsetReset) {
- this.groupId = Objects.requireNonNull(groupId, "Group ID required");
+ this.groupId = groupId;
this.topics = Collections.emptyList();
this.topicPattern = Objects.requireNonNull(topicPattern, "Topic Patten required");
this.autoOffsetReset = Objects.requireNonNull(autoOffsetReset, "Auto Offset Reset required");
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
index a03c0ecac3..61c71e0a03 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/Kafka3ConnectionService.java
@@ -30,6 +30,8 @@ import org.apache.nifi.annotation.lifecycle.OnEnabled;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.components.connector.components.ConnectorMethod;
+import org.apache.nifi.components.connector.components.MethodArgument;
import org.apache.nifi.context.PropertyContext;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ConfigurationContext;
@@ -42,6 +44,7 @@ import org.apache.nifi.kafka.service.api.consumer.KafkaConsumerService;
import org.apache.nifi.kafka.service.api.consumer.PollingContext;
import org.apache.nifi.kafka.service.api.producer.KafkaProducerService;
import org.apache.nifi.kafka.service.api.producer.ProducerConfiguration;
+import org.apache.nifi.kafka.service.consumer.Kafka3AssignmentService;
import org.apache.nifi.kafka.service.consumer.Kafka3ConsumerService;
import org.apache.nifi.kafka.service.consumer.Subscription;
import org.apache.nifi.kafka.service.producer.Kafka3ProducerService;
@@ -64,6 +67,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
@@ -235,18 +240,25 @@ public class Kafka3ConnectionService extends AbstractControllerService implement
public KafkaConsumerService getConsumerService(final PollingContext pollingContext) {
Objects.requireNonNull(pollingContext, "Polling Context required");
- final Subscription subscription = createSubscription(pollingContext);
+ final String groupId = pollingContext.getGroupId();
final Properties properties = new Properties();
properties.putAll(consumerProperties);
- properties.put(ConsumerConfig.GROUP_ID_CONFIG, subscription.getGroupId());
- properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, subscription.getAutoOffsetReset().getValue());
+ if (groupId != null) {
+ properties.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
+ }
+ properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, pollingContext.getAutoOffsetReset().getValue());
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
final ByteArrayDeserializer deserializer = new ByteArrayDeserializer();
final Consumer<byte[], byte[]> consumer = new KafkaConsumer<>(properties, deserializer, deserializer);
- return new Kafka3ConsumerService(getLogger(), consumer, subscription);
+ if (groupId == null) {
+ return new Kafka3AssignmentService(consumer, pollingContext.getTopics());
+ } else {
+ final Subscription subscription = createSubscription(pollingContext);
+ return new Kafka3ConsumerService(getLogger(), consumer, subscription);
+ }
}
private Subscription createSubscription(final PollingContext pollingContext) {
@@ -291,6 +303,26 @@ public class Kafka3ConnectionService extends AbstractControllerService implement
return uri;
}
+ @ConnectorMethod(
+ name="listTopicNames",
+ description="Returns a list of topic names available in the Kafka cluster",
+ arguments = {
+ @MethodArgument(name = "context", type = ConfigurationContext.class, description = "The configuration context that specifies connectivity details")
+ })
+ public List<String> listTopicNames(final ConfigurationContext context) throws ExecutionException, InterruptedException {
+ final Properties clientProperties = getClientProperties(context);
+ final Properties consumerProperties = getConsumerProperties(context, clientProperties);
+
+ try (final Admin admin = Admin.create(consumerProperties)) {
+ final ListTopicsResult result = admin.listTopics();
+ final Set<String> topicNames = result.names().get();
+ final List<String> sortedTopicNames = new ArrayList<>(topicNames);
+ sortedTopicNames.sort(String.CASE_INSENSITIVE_ORDER);
+ return sortedTopicNames;
+ }
+ }
+
+
@Override
public List<ConfigVerificationResult> verify(final ConfigurationContext configurationContext, final ComponentLog verificationLogger, final Map<String, String> variables) {
// Build Client Properties based on configured values and defaults from Consumer Properties
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/Kafka3AssignmentService.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/Kafka3AssignmentService.java
new file mode 100644
index 0000000000..783134ca42
--- /dev/null
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/Kafka3AssignmentService.java
@@ -0,0 +1,93 @@
+/*
+ * 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.kafka.service.consumer;
+
+import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.common.PartitionInfo;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.nifi.kafka.service.api.common.PartitionState;
+import org.apache.nifi.kafka.service.api.consumer.KafkaConsumerService;
+import org.apache.nifi.kafka.service.api.consumer.PollingSummary;
+import org.apache.nifi.kafka.service.api.record.ByteRecord;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+public class Kafka3AssignmentService implements KafkaConsumerService {
+ private final Consumer<byte[], byte[]> consumer;
+ private volatile boolean closed = false;
+
+ public Kafka3AssignmentService(final Consumer<byte[], byte[]> consumer, final Collection<String> topicNames) {
+ this.consumer = consumer;
+
+ final List<TopicPartition> topicPartitions = new ArrayList<>();
+ for (final String topicName : topicNames) {
+ final List<PartitionInfo> partitionInfos = consumer.partitionsFor(topicName);
+ partitionInfos.forEach(info -> topicPartitions.add(new TopicPartition(info.topic(), info.partition())));
+ }
+
+ consumer.assign(topicPartitions);
+ }
+
+ @Override
+ public void commit(final PollingSummary pollingSummary) {
+ throw new UnsupportedOperationException("Commit not supported for assignment-based consumer");
+ }
+
+ @Override
+ public void rollback() {
+ // no-op
+ }
+
+ @Override
+ public boolean isClosed() {
+ return false;
+ }
+
+ @Override
+ public Iterable<ByteRecord> poll(final Duration maxWaitDuration) {
+ final ConsumerRecords<byte[], byte[]> consumerRecords = consumer.poll(maxWaitDuration);
+ if (consumerRecords.isEmpty()) {
+ return List.of();
+ }
+
+ return new RecordIterable(consumerRecords);
+ }
+
+ @Override
+ public List<PartitionState> getPartitionStates() {
+ final Set<TopicPartition> topicPartitions = consumer.assignment();
+ return topicPartitions.stream()
+ .map(tp -> new PartitionState(tp.topic(), tp.partition()))
+ .toList();
+ }
+
+ @Override
+ public void close() {
+ if (closed) {
+ return;
+ }
+
+ closed = true;
+ consumer.close();
+ }
+}
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/Kafka3ConsumerService.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/Kafka3ConsumerService.java
index 59ea23818a..b1152fb249 100644
--- a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/Kafka3ConsumerService.java
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/Kafka3ConsumerService.java
@@ -18,7 +18,6 @@ package org.apache.nifi.kafka.service.consumer;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
-import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition;
@@ -27,13 +26,11 @@ import org.apache.nifi.kafka.service.api.common.PartitionState;
import org.apache.nifi.kafka.service.api.common.TopicPartitionSummary;
import org.apache.nifi.kafka.service.api.consumer.KafkaConsumerService;
import org.apache.nifi.kafka.service.api.consumer.PollingSummary;
-import org.apache.nifi.kafka.service.api.header.RecordHeader;
import org.apache.nifi.kafka.service.api.record.ByteRecord;
import org.apache.nifi.logging.ComponentLog;
import java.io.Closeable;
import java.time.Duration;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -179,58 +176,4 @@ public class Kafka3ConsumerService implements KafkaConsumerService, Closeable, C
return offsets;
}
-
-
- private static class RecordIterable implements Iterable<ByteRecord> {
- private final Iterator<ByteRecord> records;
-
- private RecordIterable(final Iterable<ConsumerRecord<byte[], byte[]>> consumerRecords) {
- this.records = new RecordIterator(consumerRecords);
- }
-
- @Override
- public Iterator<ByteRecord> iterator() {
- return records;
- }
- }
-
- private static class RecordIterator implements Iterator<ByteRecord> {
- private final Iterator<ConsumerRecord<byte[], byte[]>> consumerRecords;
-
- private RecordIterator(final Iterable<ConsumerRecord<byte[], byte[]>> records) {
- this.consumerRecords = records.iterator();
- }
-
- @Override
- public boolean hasNext() {
- return consumerRecords.hasNext();
- }
-
- @Override
- public ByteRecord next() {
- final ConsumerRecord<byte[], byte[]> consumerRecord = consumerRecords.next();
- final List<RecordHeader> recordHeaders = new ArrayList<>();
- consumerRecord.headers().forEach(header -> {
- final RecordHeader recordHeader = new RecordHeader(header.key(), header.value());
- recordHeaders.add(recordHeader);
- });
-
- // Support Kafka tombstones
- byte[] value = consumerRecord.value();
- if (value == null) {
- value = new byte[0];
- }
-
- return new ByteRecord(
- consumerRecord.topic(),
- consumerRecord.partition(),
- consumerRecord.offset(),
- consumerRecord.timestamp(),
- recordHeaders,
- consumerRecord.key(),
- value,
- 1
- );
- }
- }
}
diff --git a/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/RecordIterable.java b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/RecordIterable.java
new file mode 100644
index 0000000000..714caa06db
--- /dev/null
+++ b/nifi-extension-bundles/nifi-kafka-bundle/nifi-kafka-service-shared/src/main/java/org/apache/nifi/kafka/service/consumer/RecordIterable.java
@@ -0,0 +1,79 @@
+/*
+ * 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.kafka.service.consumer;
+
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.nifi.kafka.service.api.header.RecordHeader;
+import org.apache.nifi.kafka.service.api.record.ByteRecord;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class RecordIterable implements Iterable<ByteRecord> {
+ private final Iterator<ByteRecord> records;
+
+ public RecordIterable(final Iterable<ConsumerRecord<byte[], byte[]>> consumerRecords) {
+ this.records = new RecordIterator(consumerRecords);
+ }
+
+ @Override
+ public Iterator<ByteRecord> iterator() {
+ return records;
+ }
+
+ private static class RecordIterator implements Iterator<ByteRecord> {
+ private final Iterator<ConsumerRecord<byte[], byte[]>> consumerRecords;
+
+ private RecordIterator(final Iterable<ConsumerRecord<byte[], byte[]>> records) {
+ this.consumerRecords = records.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return consumerRecords.hasNext();
+ }
+
+ @Override
+ public ByteRecord next() {
+ final ConsumerRecord<byte[], byte[]> consumerRecord = consumerRecords.next();
+ final List<RecordHeader> recordHeaders = new ArrayList<>();
+ consumerRecord.headers().forEach(header -> {
+ final RecordHeader recordHeader = new RecordHeader(header.key(), header.value());
+ recordHeaders.add(recordHeader);
+ });
+
+ // Support Kafka tombstones
+ byte[] value = consumerRecord.value();
+ if (value == null) {
+ value = new byte[0];
+ }
+
+ return new ByteRecord(
+ consumerRecord.topic(),
+ consumerRecord.partition(),
+ consumerRecord.offset(),
+ consumerRecord.timestamp(),
+ recordHeaders,
+ consumerRecord.key(),
+ value,
+ 1
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/DuplicateFlowFile.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/DuplicateFlowFile.java
index 3297dc6217..94f8b0ac1d 100644
--- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/DuplicateFlowFile.java
+++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/DuplicateFlowFile.java
@@ -43,7 +43,7 @@ import java.util.Set;
+ "generated copies are sent to the 'success' relationship. In addition, each FlowFile gets an attribute 'copy.index' set to the copy number, where the original FlowFile gets "
+ "a value of zero, and all copies receive incremented integer values.")
@WritesAttributes({
- @WritesAttribute(attribute = "copy.index", description = "A zero-based incrementing integer value based on which copy the FlowFile is.")
+ @WritesAttribute(attribute = "copy.index", description = "A zero-based incrementing integer value based on which copy the FlowFile is.")
})
public class DuplicateFlowFile extends AbstractProcessor {
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileStatus.java b/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileStatus.java
index 737fbe3884..3233465e3c 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileStatus.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/DropFlowFileStatus.java
@@ -17,6 +17,8 @@
package org.apache.nifi.controller.queue;
+import java.util.concurrent.Future;
+
/**
* Represents the status of a Drop FlowFile Request that has been issued to
* a {@link FlowFileQueue}. When a queue is requested to drop its FlowFiles,
@@ -75,4 +77,9 @@ public interface DropFlowFileStatus {
* @return the reason that the state is set to a Failure state, or <code>null</code> if the state is not {@link DropFlowFileState#FAILURE}.
*/
String getFailureReason();
+
+ /**
+ * @return a Future that can be used to determine when the drop operation has completed
+ */
+ Future<Void> getCompletionFuture();
}
diff --git a/nifi-framework-bundle/nifi-framework-extensions/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java b/nifi-framework-bundle/nifi-framework-extensions/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java
index 8f59bd721c..9be7a17f52 100644
--- a/nifi-framework-bundle/nifi-framework-extensions/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java
+++ b/nifi-framework-bundle/nifi-framework-extensions/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java
@@ -48,7 +48,7 @@ public class TestVolatileProvenanceRepository {
@Test
public void testAddAndGet() throws IOException {
- repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties(null));
+ repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties((String) null));
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "xyz");
@@ -81,7 +81,7 @@ public class TestVolatileProvenanceRepository {
@Test
public void testIndexAndCompressOnRolloverAndSubsequentSearchAsync() throws InterruptedException {
- repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties(null));
+ repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties((String) null));
final String uuid = "00000000-0000-0000-0000-000000000000";
final Map<String, String> attributes = new HashMap<>();
@@ -120,7 +120,7 @@ public class TestVolatileProvenanceRepository {
@Test
public void testSearchForInverseValue() throws InterruptedException {
- repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties(null));
+ repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties((String) null));
final Map<String, String> attributes = new HashMap<>();
attributes.put("abc", "xyz");
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java b/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
index 0856573c9d..33a29d880b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
@@ -44,7 +44,8 @@ public enum ResourceType {
Operation("/operation"),
Tenant("/tenants"),
ParameterContext("/parameter-contexts"),
- ParameterProvider("/parameter-providers");
+ ParameterProvider("/parameter-providers"),
+ Connector("/connectors");
final String value;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java
new file mode 100644
index 0000000000..780d496a14
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ClusteredConnectorRequestReplicator.java
@@ -0,0 +1,87 @@
+/*
+ * 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.cluster.coordination.http.replication;
+
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+import jakarta.ws.rs.core.Response.Status.Family;
+import jakarta.ws.rs.core.Response.StatusType;
+import org.apache.nifi.components.connector.ConnectorRequestReplicator;
+import org.apache.nifi.components.connector.ConnectorState;
+import org.apache.nifi.controller.flow.FlowManager;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public class ClusteredConnectorRequestReplicator implements ConnectorRequestReplicator {
+ private static final String GET = "GET";
+
+ private final Supplier<RequestReplicator> requestReplicatorSupplier;
+
+ public ClusteredConnectorRequestReplicator(final Supplier<RequestReplicator> requestReplicatorSupplier) {
+ this.requestReplicatorSupplier = Objects.requireNonNull(requestReplicatorSupplier, "Request Replicator Supplier required");
+ }
+
+ @Override
+ public ConnectorState getState(final String connectorId) throws IOException {
+ final RequestReplicator requestReplicator = getRequestReplicator();
+ final AsyncClusterResponse asyncResponse = requestReplicator.replicate(GET, URI.create("/nifi-api/connectors/" + connectorId + "/status"), null, Map.of());
+ try {
+ final Response response = asyncResponse.awaitMergedResponse().getClientResponse();
+ verifyResponse(response.getStatusInfo(), connectorId);
+
+ // TODO: Need a ConnectorStatusEntity? Need correct URI.
+ return response.readEntity(ConnectorState.class);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException("Interrupted while waiting for response for status of Connector with ID " + connectorId, e);
+ }
+ }
+
+ @Override
+ public void setFlowManager(final FlowManager flowManager) {
+ }
+
+ private RequestReplicator getRequestReplicator() {
+ final RequestReplicator requestReplicator = requestReplicatorSupplier.get();
+ return Objects.requireNonNull(requestReplicator, "Request Replicator required");
+ }
+
+ private void verifyResponse(final StatusType responseStatusType, final String connectorId) throws IOException {
+ final int statusCode = responseStatusType.getStatusCode();
+ final String reason = responseStatusType.getReasonPhrase();
+
+ if (responseStatusType.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
+ throw new IllegalArgumentException("Connector with ID + " + connectorId + " does not exist");
+ }
+
+ final Family responseFamily = responseStatusType.getFamily();
+ if (responseFamily == Family.SERVER_ERROR) {
+ throw new IOException("Server-side error requesting State for Connector with ID + " + connectorId + ". Status code: " + statusCode + ", reason: " + reason);
+ }
+ if (responseFamily == Family.CLIENT_ERROR) {
+ throw new IOException("Client-side error requesting State for Connector with ID " + connectorId + ". Status code: " + statusCode + ", reason: " + reason);
+ }
+ if (responseFamily != Family.SUCCESSFUL) {
+ throw new IOException("Unexpected response code " + statusCode + " returned when requesting State for Connector with ID " + connectorId + ": " + reason);
+ }
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/ConnectorRequestReplicatorConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/ConnectorRequestReplicatorConfiguration.java
new file mode 100644
index 0000000000..4024321bd1
--- /dev/null
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/ConnectorRequestReplicatorConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * 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.framework.configuration;
+
+import org.apache.nifi.cluster.coordination.http.replication.ClusteredConnectorRequestReplicator;
+import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
+import org.apache.nifi.components.connector.ConnectorRequestReplicator;
+import org.apache.nifi.components.connector.StandaloneConnectorRequestReplicator;
+import org.apache.nifi.util.NiFiProperties;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ConnectorRequestReplicatorConfiguration {
+
+ @Bean
+ public ConnectorRequestReplicator connectorRequestReplicator(
+ final NiFiProperties nifiProperties,
+ final ObjectProvider<RequestReplicator> requestReplicatorProvider) {
+
+ if (nifiProperties.isClustered()) {
+ // We have to use an ObjectProvider here and obtain a Supplier because of a circular dependency.
+ // The request replicator will not be available when created. However, it will be
+ // available before attempting to use the ConnectorRequestReplicator.
+ return new ClusteredConnectorRequestReplicator(requestReplicatorProvider::getIfAvailable);
+ }
+
+ return new StandaloneConnectorRequestReplicator();
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/FrameworkClusterConfiguration.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/FrameworkClusterConfiguration.java
index 1bdbde5093..b0eb1edd9d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/FrameworkClusterConfiguration.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/framework/configuration/FrameworkClusterConfiguration.java
@@ -48,8 +48,6 @@ public class FrameworkClusterConfiguration {
private EventReporter eventReporter;
- private FlowController flowController;
-
private ClusterCoordinator clusterCoordinator;
private WebClientService webClientService;
@@ -59,11 +57,6 @@ public class FrameworkClusterConfiguration {
this.properties = properties;
}
- @Autowired
- public void setFlowController(final FlowController flowController) {
- this.flowController = flowController;
- }
-
@Autowired
public void setEventReporter(final EventReporter eventReporter) {
this.eventReporter = eventReporter;
@@ -106,7 +99,7 @@ public class FrameworkClusterConfiguration {
}
@Bean
- public ClusterDecommissionTask decommissionTask() {
+ public ClusterDecommissionTask decommissionTask(final FlowController flowController) {
return new ClusterDecommissionTask(clusterCoordinator, flowController);
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
index a21cd535c1..9bdde6d3d9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
@@ -268,7 +268,7 @@ public class TestThreadPoolRequestReplicator {
when(coordinator.getConnectionStatus(Mockito.any(NodeIdentifier.class))).thenReturn(new NodeConnectionStatus(nodeId, NodeConnectionState.CONNECTED));
final AtomicInteger requestCount = new AtomicInteger(0);
- final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null);
+ final NiFiProperties props = NiFiProperties.createBasicNiFiProperties((String) null);
final MockReplicationClient client = new MockReplicationClient();
final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
@@ -329,7 +329,7 @@ public class TestThreadPoolRequestReplicator {
final ClusterCoordinator coordinator = createClusterCoordinator();
final AtomicInteger requestCount = new AtomicInteger(0);
- final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null);
+ final NiFiProperties props = NiFiProperties.createBasicNiFiProperties((String) null);
final MockReplicationClient client = new MockReplicationClient();
final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
@@ -529,7 +529,7 @@ public class TestThreadPoolRequestReplicator {
private void withReplicator(final WithReplicator function, final Status status, final long delayMillis, final RuntimeException failure,
final String expectedRequestChain, final String expectedProxiedEntityGroups) {
final ClusterCoordinator coordinator = createClusterCoordinator();
- final NiFiProperties nifiProps = NiFiProperties.createBasicNiFiProperties(null);
+ final NiFiProperties nifiProps = NiFiProperties.createBasicNiFiProperties((String) null);
final MockReplicationClient client = new MockReplicationClient();
final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
};
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
index 20f5363744..b7e431aa27 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
@@ -42,12 +42,18 @@ import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.InvocationFailedException;
+import org.apache.nifi.components.connector.components.ComponentState;
+import org.apache.nifi.components.connector.components.ConnectorMethod;
+import org.apache.nifi.components.connector.components.MethodArgument;
import org.apache.nifi.components.validation.ValidationState;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.components.validation.ValidationTrigger;
import org.apache.nifi.connectable.Connectable;
+import org.apache.nifi.connectable.ConnectableFlowFileActivity;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.connectable.FlowFileActivity;
import org.apache.nifi.connectable.Position;
import org.apache.nifi.controller.exception.ProcessorInstantiationException;
import org.apache.nifi.controller.scheduling.LifecycleState;
@@ -178,6 +184,8 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
private volatile BackoffMechanism backoffMechanism;
private volatile String maxBackoffPeriod;
+ private final ConnectableFlowFileActivity flowFileActivity = new ConnectableFlowFileActivity();
+
public StandardProcessorNode(final LoggableComponent<Processor> processor, final String uuid,
final ValidationContextFactory validationContextFactory, final ProcessScheduler scheduler,
final ControllerServiceProvider controllerServiceProvider, final ReloadComponent reloadComponent,
@@ -977,7 +985,9 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
}
@Override
- public List<ConfigVerificationResult> verifyConfiguration(final ProcessContext context, final ComponentLog logger, final Map<String, String> attributes, final ExtensionManager extensionManager) {
+ public List<ConfigVerificationResult> verifyConfiguration(final ProcessContext context, final ComponentLog logger, final Map<String, String> attributes, final ExtensionManager extensionManager,
+ final ParameterLookup parameterLookup) {
+
final List<ConfigVerificationResult> results = new ArrayList<>();
try {
@@ -985,7 +995,7 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
final long startNanos = System.nanoTime();
// Call super's verifyConfig, which will perform component validation
- results.addAll(super.verifyConfig(context.getProperties(), context.getAnnotationData(), getProcessGroup().getParameterContext()));
+ results.addAll(super.verifyConfig(context.getProperties(), context.getAnnotationData(), parameterLookup));
final long validationComplete = System.nanoTime();
// If any invalid outcomes from validation, we do not want to perform additional verification, because we only run additional verification when the component is valid.
@@ -1628,6 +1638,9 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
// Completion Timestamp is set to MAX_VALUE because we don't want to timeout until the task has a chance to run.
final AtomicLong completionTimestampRef = new AtomicLong(Long.MAX_VALUE);
+ // Mark current time as latest activity time so that we don't show as idle when the processor was stopped.
+ flowFileActivity.reset();
+
// Create a task to invoke the @OnScheduled annotation of the processor
final Callable<Void> startupTask = () -> {
final ScheduledState currentScheduleState = scheduledState.get();
@@ -1922,6 +1935,86 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
}
}
+ @Override
+ public List<ConnectorMethod> getConnectorMethods() {
+ return getConnectorMethods(getProcessor().getClass());
+ }
+
+ public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final ProcessContext processContext) throws InvocationFailedException {
+ final ConfigurableComponent component = getComponent();
+
+ try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(getExtensionManager(), component.getClass(), getIdentifier())) {
+ final Method implementationMethod = discoverConnectorMethod(component.getClass(), methodName);
+ final MethodArgument[] methodArguments = getConnectorMethodArguments(methodName, implementationMethod, component);
+ final List<Object> argumentValues = new ArrayList<>();
+ for (final MethodArgument methodArgument : methodArguments) {
+ final Object argumentValue = arguments.get(methodArgument.name());
+ if (ProcessContext.class.equals(methodArgument.type())) {
+ continue;
+ }
+
+ if (argumentValue == null && methodArgument.required()) {
+ throw new IllegalArgumentException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because the required argument '"
+ + methodArgument.name() + "' was not provided");
+ }
+
+ if (argumentValue != null && !(methodArgument.type().isAssignableFrom(argumentValue.getClass()))) {
+ throw new IllegalArgumentException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because the argument '"
+ + methodArgument.name() + "' is of type " + argumentValue.getClass().getName() + " defined by " + argumentValue.getClass().getClassLoader()
+ + " but the method expects type " + methodArgument.type().getName() + " defined by " + methodArgument.type().getClassLoader());
+ }
+
+ argumentValues.add(argumentValue);
+ }
+
+ // Inject ProcessContext if the method signature supports it
+ final Class<?>[] argumentTypes = implementationMethod.getParameterTypes();
+ if (argumentTypes.length > 0 && ProcessContext.class.isAssignableFrom(argumentTypes[0])) {
+ argumentValues.addFirst(processContext);
+ }
+ if (argumentTypes.length > 1 && ProcessContext.class.isAssignableFrom(argumentTypes[argumentTypes.length - 1])) {
+ argumentValues.add(processContext);
+ }
+
+ try {
+ implementationMethod.setAccessible(true);
+ return implementationMethod.invoke(component, argumentValues.toArray());
+ } catch (final Exception e) {
+ throw new InvocationFailedException(e);
+ }
+ }
+ }
+
+ private MethodArgument[] getConnectorMethodArguments(final String methodName, final Method implementationMethod, final ConfigurableComponent component) throws InvocationFailedException {
+ if (implementationMethod == null) {
+ throw new InvocationFailedException("No such connector method '" + methodName + "' exists for " + component.getClass().getName());
+ }
+
+ final ConnectorMethod connectorMethodDefinition = implementationMethod.getAnnotation(ConnectorMethod.class);
+ final ComponentState[] componentStates = connectorMethodDefinition.allowedStates();
+ final ComponentState currentState = getComponentState();
+ final boolean validState = Set.of(componentStates).contains(currentState);
+ if (!validState) {
+ throw new IllegalStateException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because Processor is in state " + currentState
+ + " but the Connector Method does not allow invocation in this state");
+ }
+
+ final MethodArgument[] methodArguments = connectorMethodDefinition.arguments();
+ return methodArguments;
+ }
+
+ private ComponentState getComponentState() {
+ final ScheduledState scheduledState = getScheduledState();
+
+ return switch (scheduledState) {
+ case DISABLED -> ComponentState.PROCESSOR_DISABLED;
+ case STOPPED -> ComponentState.STOPPED;
+ case RUNNING, RUN_ONCE -> ComponentState.RUNNING;
+ case STARTING -> ComponentState.STARTING;
+ case STOPPING -> ComponentState.STOPPING;
+ };
+ }
+
/**
* Marks the processor as fully stopped, and completes any futures that are to be completed as a result
*/
@@ -2167,4 +2260,9 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable
serviceNode.updateReference(this, descriptor);
}
}
+
+ @Override
+ public FlowFileActivity getFlowFileActivity() {
+ return flowFileActivity;
+ }
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
index 50308465d2..a08000428c 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flow/AbstractFlowManager.java
@@ -637,7 +637,7 @@ public abstract class AbstractFlowManager implements FlowManager {
throw new IllegalStateException("Cannot create Parameter Context with name '" + name + "' because a Parameter Context already exists with that name");
}
- final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(this);
+ final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(this::getRootGroup);
final ParameterContext parameterContext = new StandardParameterContext.Builder()
.id(id)
.name(name)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flowanalysis/FlowAnalysisUtil.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flowanalysis/FlowAnalysisUtil.java
index 7c8e8963b8..6e6c58bd92 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flowanalysis/FlowAnalysisUtil.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/flowanalysis/FlowAnalysisUtil.java
@@ -19,13 +19,13 @@ package org.apache.nifi.controller.flowanalysis;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.registry.flow.mapping.ComponentIdLookup;
import org.apache.nifi.registry.flow.mapping.FlowMappingOptions;
-import org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper;
+import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
public class FlowAnalysisUtil {
public static final String ENCRYPTED_SENSITIVE_VALUE_SUBSTITUTE = "*****";
- public static NiFiRegistryFlowMapper createMapper(ExtensionManager extensionManager) {
+ public static VersionedComponentFlowMapper createMapper(ExtensionManager extensionManager) {
final FlowMappingOptions flowMappingOptions = new FlowMappingOptions.Builder()
.mapPropertyDescriptors(true)
.mapControllerServiceReferencesToVersionedId(true)
@@ -36,7 +36,7 @@ public class FlowAnalysisUtil {
.mapAssetReferences(true)
.build();
- final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(extensionManager, flowMappingOptions) {
+ final VersionedComponentFlowMapper mapper = new VersionedComponentFlowMapper(extensionManager, flowMappingOptions) {
@Override
public String getGroupId(String groupId) {
return groupId;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
index a12f7a626e..f8a1014f39 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
@@ -596,7 +596,10 @@ public class StandardProcessSession implements ProcessSession, ProvenanceEventEn
// Update the FlowFile Repository
try {
final Collection<StandardRepositoryRecord> repoRecords = checkpoint.records.values();
- context.getFlowFileRepository().updateRepository((Collection) repoRecords);
+ if (!repoRecords.isEmpty()) {
+ context.getFlowFileRepository().updateRepository((Collection) repoRecords);
+ context.getConnectable().getFlowFileActivity().updateLatestActivityTime();
+ }
} catch (final IOException ioe) {
// if we fail to commit the session, we need to roll back
// the checkpoints as well because none of the checkpoints
@@ -974,7 +977,7 @@ public class StandardProcessSession implements ProcessSession, ProvenanceEventEn
// Therefore, we create an Iterable that can iterate over each of these events, modifying them as needed, and returning them
// in the appropriate order. This prevents an unnecessary step of creating an intermediate List and adding all of those values
// to the List.
- // This is done in a similar veign to how Java 8's streams work, iterating over the events and returning a processed version
+ // This is done in a similar vein to how Java 8's streams work, iterating over the events and returning a processed version
// one-at-a-time as opposed to iterating over the entire Collection and putting the results in another Collection. However,
// we don't want to change the Framework to require Java 8 at this time, because it's not yet as prevalent as we would desire
final Map<String, FlowFileRecord> flowFileRecordMap = new HashMap<>();
@@ -983,6 +986,9 @@ public class StandardProcessSession implements ProcessSession, ProvenanceEventEn
flowFileRecordMap.put(flowFile.getAttribute(CoreAttributes.UUID.key()), flowFile);
}
+ // Update connectable to indicate how much data was received and sent.
+ updateTransferCounts(recordsToSubmit);
+
final long commitNanos = System.nanoTime();
final List<ProvenanceEventRecord> autoTermEvents = checkpoint.autoTerminatedEvents;
final Iterable<ProvenanceEventRecord> iterable = new Iterable<>() {
@@ -1029,6 +1035,23 @@ public class StandardProcessSession implements ProcessSession, ProvenanceEventEn
provenanceRepo.registerEvents(iterable);
}
+ private void updateTransferCounts(final Collection<ProvenanceEventRecord> events) {
+ int createdCount = 0;
+ long createdBytes = 0L;
+ for (final ProvenanceEventRecord event : events) {
+ if (event.getEventType() == ProvenanceEventType.CREATE) {
+ createdCount++;
+ createdBytes += event.getFileSize();
+ }
+ }
+
+ context.getConnectable().getFlowFileActivity().updateTransferCounts(
+ provenanceReporter.getFlowFilesReceived() + createdCount,
+ // For bytes, consider received and fetched; we don't include fetched in count, as the FlowFile count didn't change.
+ provenanceReporter.getBytesReceived() + provenanceReporter.getBytesFetched() + createdBytes,
+ provenanceReporter.getFlowFilesSent(),
+ provenanceReporter.getBytesSent());
+ }
private void updateEventContentClaims(final ProvenanceEventBuilder builder, final FlowFile flowFile, final StandardRepositoryRecord repoRecord) {
final ContentClaim originalClaim = repoRecord.getOriginalClaim();
@@ -1237,6 +1260,10 @@ public class StandardProcessSession implements ProcessSession, ProvenanceEventEn
resetWriteClaims();
resetReadClaim();
+ if (!recordValues.isEmpty()) {
+ context.getConnectable().getFlowFileActivity().updateLatestActivityTime();
+ }
+
if (recordsToHandle.isEmpty()) {
LOG.trace("{} was rolled back, but no events were performed by this ProcessSession", this);
acknowledgeRecords();
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInvocationHandler.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInvocationHandler.java
index 9b87040540..3cb82157ca 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInvocationHandler.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceInvocationHandler.java
@@ -17,6 +17,8 @@
package org.apache.nifi.controller.service;
import org.apache.commons.lang3.ClassUtils;
+import org.apache.nifi.components.connector.components.ComponentState;
+import org.apache.nifi.components.connector.components.ConnectorMethod;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.ControllerServiceProxyWrapper;
import org.apache.nifi.nar.ExtensionManager;
@@ -29,7 +31,6 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -38,15 +39,7 @@ import java.util.concurrent.atomic.AtomicReference;
public class StandardControllerServiceInvocationHandler implements ControllerServiceInvocationHandler {
private static final Logger logger = LoggerFactory.getLogger(StandardControllerServiceInvocationHandler.class);
private static final Method PROXY_WRAPPER_GET_WRAPPED_METHOD;
-
- private static final Set<Method> validDisabledMethods;
static {
- // methods that are okay to be called when the service is disabled.
- final Set<Method> validMethods = new HashSet<>();
- validMethods.addAll(Arrays.asList(ControllerService.class.getMethods()));
- validMethods.addAll(Arrays.asList(Object.class.getMethods()));
- validDisabledMethods = Collections.unmodifiableSet(validMethods);
-
try {
PROXY_WRAPPER_GET_WRAPPED_METHOD = ControllerServiceProxyWrapper.class.getMethod("getWrapped");
} catch (final NoSuchMethodException e) {
@@ -54,6 +47,7 @@ public class StandardControllerServiceInvocationHandler implements ControllerSer
}
}
+ private final Set<Method> validDisabledMethods;
private final ControllerService originalService;
private final AtomicReference<ControllerServiceNode> serviceNodeHolder = new AtomicReference<>(null);
private final ExtensionManager extensionManager;
@@ -73,6 +67,34 @@ public class StandardControllerServiceInvocationHandler implements ControllerSer
this.extensionManager = extensionManager;
this.originalService = originalService;
this.serviceNodeHolder.set(serviceNode);
+ validDisabledMethods = determineDisabledAllowedMethods(originalService);
+ }
+
+ private Set<Method> determineDisabledAllowedMethods(final ControllerService implementation) {
+ // methods that are okay to be called when the service is disabled.
+ final Set<Method> validMethods = new HashSet<>();
+ validMethods.addAll(Arrays.asList(ControllerService.class.getMethods()));
+ validMethods.addAll(Arrays.asList(Object.class.getMethods()));
+
+ // If there are any Connector Methods that are allowed to be called when the service is STOPPED or STOPPING, add those as well.
+ // This allows us to perform actions from Connectors, such as obtaining a list of available resources, during Connector configuration, etc.
+ // while the service is currently stopped.
+ for (final Method method : implementation.getClass().getDeclaredMethods()) {
+ final ConnectorMethod connectorMethod = method.getAnnotation(ConnectorMethod.class);
+ if (connectorMethod == null) {
+ continue;
+ }
+
+ final ComponentState[] allowedStates = connectorMethod.allowedStates();
+ for (final ComponentState allowedState : allowedStates) {
+ if (allowedState == ComponentState.STOPPED || allowedState == ComponentState.STOPPING) {
+ validMethods.add(method);
+ break;
+ }
+ }
+ }
+
+ return validMethods;
}
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
index a530513cf0..25c6f03da9 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
@@ -35,6 +35,10 @@ import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.connector.InvocationFailedException;
+import org.apache.nifi.components.connector.components.ComponentState;
+import org.apache.nifi.components.connector.components.ConnectorMethod;
+import org.apache.nifi.components.connector.components.MethodArgument;
import org.apache.nifi.components.validation.ValidationState;
import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.components.validation.ValidationTrigger;
@@ -95,6 +99,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
public class StandardControllerServiceNode extends AbstractComponentNode implements ControllerServiceNode {
@@ -504,7 +509,7 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
@Override
public List<ConfigVerificationResult> verifyConfiguration(final ConfigurationContext context, final ComponentLog logger, final Map<String, String> variables,
- final ExtensionManager extensionManager) {
+ final ExtensionManager extensionManager, final ParameterLookup parameterLookup) {
final List<ConfigVerificationResult> results = new ArrayList<>();
@@ -513,7 +518,7 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
final long startNanos = System.nanoTime();
// Call super's verifyConfig, which will perform component validation
- results.addAll(super.verifyConfig(context.getProperties(), context.getAnnotationData(), getProcessGroup() == null ? null : getProcessGroup().getParameterContext()));
+ results.addAll(super.verifyConfig(context.getProperties(), context.getAnnotationData(), parameterLookup));
final long validationComplete = System.nanoTime();
// If any invalid outcomes from validation, we do not want to perform additional verification, because we only run additional verification when the component is valid.
@@ -673,6 +678,7 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
LOG.debug("Validation rescheduling rejected for {}", serviceNode, e);
future.completeExceptionally(new IllegalStateException("Enabling %s rejected: Validation Status [%s] Errors %s".formatted(serviceNode, validationStatus, errors)));
}
+
// Enable command rescheduled or rejected
return;
}
@@ -899,6 +905,88 @@ public class StandardControllerServiceNode extends AbstractComponentNode impleme
}
}
+ // TODO: Refactor, this is the same between ProcessorNode / ControllerServiceNode except for getComponentState
+ @Override
+ public Object invokeConnectorMethod(final String methodName, final Map<String, Object> arguments, final ConfigurationContext configurationContext) throws InvocationFailedException {
+ final ConfigurableComponent component = getComponent();
+
+ try (final NarCloseable ignored = NarCloseable.withComponentNarLoader(getExtensionManager(), component.getClass(), getIdentifier())) {
+ final Method implementationMethod = discoverConnectorMethod(component.getClass(), methodName);
+ final MethodArgument[] methodArguments = getConnectorMethodArguments(methodName, implementationMethod, component);
+ final List<Object> argumentValues = new ArrayList<>();
+ for (final MethodArgument methodArgument : methodArguments) {
+ final Object argumentValue = arguments.get(methodArgument.name());
+ if (ConfigurationContext.class.equals(methodArgument.type())) {
+ continue;
+ }
+
+ if (argumentValue == null && methodArgument.required()) {
+ throw new IllegalArgumentException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because the required argument '"
+ + methodArgument.name() + "' was not provided");
+ }
+
+ if (argumentValue != null && !(methodArgument.type().isAssignableFrom(argumentValue.getClass()))) {
+ throw new IllegalArgumentException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because the argument '"
+ + methodArgument.name() + "' is of type " + argumentValue.getClass().getName() + " defined by " + argumentValue.getClass().getClassLoader()
+ + " but the method expects type " + methodArgument.type().getName() + " defined by " + methodArgument.type().getClassLoader());
+ }
+
+ argumentValues.add(argumentValue);
+ }
+
+ // Inject ConfigurationContext if the method signature supports it
+ // TODO: Can we move away from Maps and instead just use reflection to get the arguments directly?
+ final Class<?>[] argumentTypes = implementationMethod.getParameterTypes();
+ if (argumentTypes.length > 0 && ConfigurationContext.class.isAssignableFrom(argumentTypes[0])) {
+ argumentValues.addFirst(configurationContext);
+ }
+ if (argumentTypes.length > 1 && ConfigurationContext.class.isAssignableFrom(argumentTypes[argumentTypes.length - 1])) {
+ argumentValues.add(configurationContext);
+ }
+
+ try {
+ implementationMethod.setAccessible(true);
+ return implementationMethod.invoke(component, argumentValues.toArray());
+ } catch (final Exception e) {
+ throw new InvocationFailedException(e);
+ }
+ }
+ }
+
+ @Override
+ public List<ConnectorMethod> getConnectorMethods() {
+ return getConnectorMethods(getControllerServiceImplementation().getClass());
+ }
+
+ private MethodArgument[] getConnectorMethodArguments(final String methodName, final Method implementationMethod, final ConfigurableComponent component) throws InvocationFailedException {
+ if (implementationMethod == null) {
+ throw new InvocationFailedException("No such connector method '" + methodName + "' exists for " + component.getClass().getName());
+ }
+
+ final ConnectorMethod connectorMethodDefinition = implementationMethod.getAnnotation(ConnectorMethod.class);
+ final ComponentState[] componentStates = connectorMethodDefinition.allowedStates();
+ final ComponentState currentState = getComponentState();
+ final boolean validState = Set.of(componentStates).contains(currentState);
+ if (!validState) {
+ throw new IllegalStateException("Cannot invoke Connector Method '" + methodName + "' on " + this + " because Processor is in state " + currentState
+ + " but the Connector Method does not allow invocation in this state");
+ }
+
+ final MethodArgument[] methodArguments = connectorMethodDefinition.arguments();
+ return methodArguments;
+ }
+
+ private ComponentState getComponentState() {
+ final ControllerServiceState scheduledState = getState();
+
+ return switch (scheduledState) {
+ case DISABLED -> ComponentState.STOPPED;
+ case DISABLING -> ComponentState.STOPPING;
+ case ENABLING -> ComponentState.STARTING;
+ case ENABLED -> ComponentState.RUNNING;
+ };
+ }
+
@Override
protected void performFlowAnalysisOnThis() {
getValidationContextFactory().getFlowAnalyzer().ifPresent(flowAnalyzer -> flowAnalyzer.analyzeControllerService(this));
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
index f1945c8ed5..95b3f34afa 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
@@ -320,7 +320,7 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
}
@Override
- public Future<Void> enableControllerServicesAsync(final Collection<ControllerServiceNode> serviceNodes) {
+ public CompletableFuture<Void> enableControllerServicesAsync(final Collection<ControllerServiceNode> serviceNodes) {
final CompletableFuture<Void> future = new CompletableFuture<>();
processScheduler.submitFrameworkTask(() -> {
try {
@@ -392,7 +392,7 @@ public class StandardControllerServiceProvider implements ControllerServiceProvi
}
@Override
- public Future<Void> enableControllerServiceAndDependencies(final ControllerServiceNode serviceNode) {
+ public CompletableFuture<Void> enableControllerServiceAndDependencies(final ControllerServiceNode serviceNode) {
if (serviceNode.isActive()) {
logger.debug("Enabling of Controller Service {} triggered but service already enabled", serviceNode);
return CompletableFuture.completedFuture(null);
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceResolver.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceResolver.java
index 7b165b8a6c..442d301fdd 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceResolver.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceResolver.java
@@ -30,7 +30,7 @@ import org.apache.nifi.flow.VersionedPropertyDescriptor;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.registry.flow.FlowSnapshotContainer;
import org.apache.nifi.registry.flow.RegisteredFlowSnapshot;
-import org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper;
+import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import java.util.ArrayList;
import java.util.Collection;
@@ -47,13 +47,13 @@ public class StandardControllerServiceResolver implements ControllerServiceResol
private final Authorizer authorizer;
private final FlowManager flowManager;
- private final NiFiRegistryFlowMapper flowMapper;
+ private final VersionedComponentFlowMapper flowMapper;
private final ControllerServiceProvider controllerServiceProvider;
private final ControllerServiceApiLookup controllerServiceApiLookup;
public StandardControllerServiceResolver(final Authorizer authorizer,
final FlowManager flowManager,
- final NiFiRegistryFlowMapper flowMapper,
+ final VersionedComponentFlowMapper flowMapper,
final ControllerServiceProvider controllerServiceProvider,
final ControllerServiceApiLookup controllerServiceApiLookup) {
this.authorizer = authorizer;
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/events/VolatileBulletinRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/events/VolatileBulletinRepository.java
index 7ea59751e8..3779f5b344 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/events/VolatileBulletinRepository.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/events/VolatileBulletinRepository.java
@@ -290,6 +290,7 @@ public class VolatileBulletinRepository implements BulletinRepository {
case FLOW_ANALYSIS_RULE -> FLOW_ANALYSIS_RULE_BULLETIN_STORE_KEY;
case PARAMETER_PROVIDER -> PARAMETER_PROVIDER_BULLETIN_STORE_KEY;
case FLOW_REGISTRY_CLIENT -> FLOW_REGISTRY_CLIENT_STORE_KEY;
+ case CONNECTOR -> bulletin.getSourceId();
default -> bulletin.getGroupId();
};
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
index 0d9775fadd..3466d317e4 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
@@ -112,7 +112,7 @@ import org.apache.nifi.registry.flow.diff.FlowDifference;
import org.apache.nifi.registry.flow.diff.StandardComparableDataFlow;
import org.apache.nifi.registry.flow.diff.StandardFlowComparator;
import org.apache.nifi.registry.flow.diff.StaticDifferenceDescriptor;
-import org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper;
+import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.remote.PublicPort;
import org.apache.nifi.remote.RemoteGroupPort;
import org.apache.nifi.remote.StandardRemoteProcessGroupPortDescriptor;
@@ -311,7 +311,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
@Override
public void synchronize(final ProcessGroup group, final VersionedExternalFlow versionedExternalFlow, final FlowSynchronizationOptions options) {
- final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(context.getExtensionManager(), context.getFlowMappingOptions());
+ final VersionedComponentFlowMapper mapper = new VersionedComponentFlowMapper(context.getExtensionManager(), context.getFlowMappingOptions());
final VersionedProcessGroup versionedGroup = mapper.mapProcessGroup(group, context.getControllerServiceProvider(), context.getFlowManager(), true);
final ComparableDataFlow localFlow = new StandardComparableDataFlow("Currently Loaded Flow", versionedGroup);
@@ -998,7 +998,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
private <T extends Connectable> Map<String, T> componentsById(final ProcessGroup group, final Function<ProcessGroup, Collection<T>> retrieveComponents) {
return retrieveComponents.apply(group).stream()
.collect(Collectors.toMap(component -> component.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
+ VersionedComponentFlowMapper.generateVersionedComponentId(component.getIdentifier())), Function.identity()));
}
private <T> Map<String, T> componentsById(final ProcessGroup group, final Function<ProcessGroup, Collection<T>> retrieveComponents,
@@ -1006,7 +1006,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
return retrieveComponents.apply(group).stream()
.collect(Collectors.toMap(component -> retrieveVersionedComponentId.apply(component).orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(retrieveId.apply(component))), Function.identity()));
+ VersionedComponentFlowMapper.generateVersionedComponentId(retrieveId.apply(component))), Function.identity()));
}
private void synchronizeFunnels(final ProcessGroup group, final VersionedProcessGroup proposed, final Map<String, Funnel> funnelsByVersionedId) {
@@ -1242,7 +1242,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
final Map<String, Port> removedInputPortsByVersionId = new HashMap<>();
group.getInputPorts()
.forEach(port -> removedInputPortsByVersionId.put(port.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(port.getIdentifier())), port));
+ VersionedComponentFlowMapper.generateVersionedComponentId(port.getIdentifier())), port));
flowContents.getInputPorts().stream()
.map(VersionedPort::getIdentifier)
@@ -1261,7 +1261,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
final Map<String, Port> removedOutputPortsByVersionId = new HashMap<>();
group.getOutputPorts()
.forEach(port -> removedOutputPortsByVersionId.put(port.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(port.getIdentifier())), port));
+ VersionedComponentFlowMapper.generateVersionedComponentId(port.getIdentifier())), port));
flowContents.getOutputPorts().stream()
.map(VersionedPort::getIdentifier)
@@ -1288,7 +1288,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
group.findAllProcessors()
.forEach(proc -> proposedProcessors.remove(proc.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(proc.getIdentifier()))));
+ VersionedComponentFlowMapper.generateVersionedComponentId(proc.getIdentifier()))));
for (final VersionedProcessor processorToAdd : proposedProcessors.values()) {
final String processorToAddClass = processorToAdd.getType();
@@ -1319,7 +1319,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
group.findAllControllerServices()
.forEach(service -> proposedServices.remove(service.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(service.getIdentifier()))));
+ VersionedComponentFlowMapper.generateVersionedComponentId(service.getIdentifier()))));
for (final VersionedControllerService serviceToAdd : proposedServices.values()) {
final String serviceToAddClass = serviceToAdd.getType();
@@ -1348,7 +1348,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
group.findAllConnections()
.forEach(conn -> proposedConnections.remove(conn.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(conn.getIdentifier()))));
+ VersionedComponentFlowMapper.generateVersionedComponentId(conn.getIdentifier()))));
for (final VersionedConnection connectionToAdd : proposedConnections.values()) {
if (connectionToAdd.getPrioritizers() != null) {
@@ -3030,7 +3030,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
private String getServiceInstanceId(final String serviceVersionedComponentId, final ProcessGroup group) {
for (final ControllerServiceNode serviceNode : group.getControllerServices(false)) {
final String versionedId = serviceNode.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(serviceNode.getIdentifier()));
+ VersionedComponentFlowMapper.generateVersionedComponentId(serviceNode.getIdentifier()));
if (versionedId.equals(serviceVersionedComponentId)) {
return serviceNode.getIdentifier();
}
@@ -3650,7 +3650,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
final String rpgId = connectableComponent.getGroupId();
final Optional<RemoteProcessGroup> rpgOption = group.getRemoteProcessGroups().stream()
.filter(component -> rpgId.equals(component.getIdentifier()) || rpgId.equals(component.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+ VersionedComponentFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
.findAny();
if (rpgOption.isEmpty()) {
@@ -3676,7 +3676,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
final String rpgId = connectableComponent.getGroupId();
final Optional<RemoteProcessGroup> rpgOption = group.getRemoteProcessGroups().stream()
.filter(component -> rpgId.equals(component.getIdentifier()) || rpgId.equals(component.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
+ VersionedComponentFlowMapper.generateVersionedComponentId(component.getIdentifier()))))
.findAny();
if (rpgOption.isEmpty()) {
@@ -3877,12 +3877,12 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
}
private <T extends org.apache.nifi.components.VersionedComponent & Connectable> boolean matchesId(final T component, final String id) {
- return id.equals(component.getIdentifier()) || id.equals(component.getVersionedComponentId().orElse(NiFiRegistryFlowMapper.generateVersionedComponentId(component.getIdentifier())));
+ return id.equals(component.getIdentifier()) || id.equals(component.getVersionedComponentId().orElse(VersionedComponentFlowMapper.generateVersionedComponentId(component.getIdentifier())));
}
private boolean matchesGroupId(final ProcessGroup group, final String groupId) {
return groupId.equals(group.getIdentifier()) || group.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(group.getIdentifier())).equals(groupId);
+ VersionedComponentFlowMapper.generateVersionedComponentId(group.getIdentifier())).equals(groupId);
}
private void findAllProcessors(final Set<VersionedProcessor> processors, final Set<VersionedProcessGroup> childGroups, final Map<String, VersionedProcessor> map) {
@@ -3934,7 +3934,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
// match group's current connections to proposed connections to determine if they've been removed
for (final Connection connection : processGroup.getConnections()) {
final String versionedId = connection.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(connection.getIdentifier()));
+ VersionedComponentFlowMapper.generateVersionedComponentId(connection.getIdentifier()));
final VersionedConnection proposedConnection = proposedConnectionsByVersionedId.get(versionedId);
if (proposedConnection == null) {
// connection doesn't exist in proposed connections, make sure it doesn't have any data in it
@@ -3953,7 +3953,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
// match current child groups to proposed child groups to determine if they've been removed
for (final ProcessGroup childGroup : processGroup.getProcessGroups()) {
final String versionedId = childGroup.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(childGroup.getIdentifier()));
+ VersionedComponentFlowMapper.generateVersionedComponentId(childGroup.getIdentifier()));
final VersionedProcessGroup proposedChildGroup = proposedGroupsByVersionedId.get(versionedId);
if (proposedChildGroup == null) {
if (verifyConnectionRemoval) {
@@ -3979,7 +3979,7 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
for (final ControllerServiceNode serviceNode : group.getControllerServices(false)) {
final String serviceNodeVersionedComponentId = serviceNode.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(serviceNode.getIdentifier()));
+ VersionedComponentFlowMapper.generateVersionedComponentId(serviceNode.getIdentifier()));
if (serviceNodeVersionedComponentId.equals(versionedComponentId)) {
return serviceNode;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
index bc8d717645..9fbfde6f0a 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java
@@ -34,11 +34,13 @@ import org.apache.nifi.components.validation.ValidationStatus;
import org.apache.nifi.connectable.Connectable;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.connectable.FlowFileActivity;
import org.apache.nifi.connectable.Funnel;
import org.apache.nifi.connectable.LocalPort;
import org.apache.nifi.connectable.Port;
import org.apache.nifi.connectable.Position;
import org.apache.nifi.connectable.Positionable;
+import org.apache.nifi.connectable.ProcessGroupFlowFileActivity;
import org.apache.nifi.controller.ComponentNode;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.ControllerService;
@@ -105,7 +107,7 @@ import org.apache.nifi.registry.flow.diff.StandardComparableDataFlow;
import org.apache.nifi.registry.flow.diff.StandardFlowComparator;
import org.apache.nifi.registry.flow.mapping.ComponentIdLookup;
import org.apache.nifi.registry.flow.mapping.FlowMappingOptions;
-import org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper;
+import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.registry.flow.mapping.VersionedComponentStateLookup;
import org.apache.nifi.remote.PublicPort;
import org.apache.nifi.remote.RemoteGroupPort;
@@ -138,6 +140,7 @@ import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -196,6 +199,8 @@ public final class StandardProcessGroup implements ProcessGroup {
private volatile ExecutionEngine executionEngine = ExecutionEngine.INHERITED;
private volatile int maxConcurrentTasks = 1;
private volatile String statelessFlowTimeout = "1 min";
+ private volatile Authorizable explicitParentAuthorizable;
+ private final FlowFileActivity flowFileActivity = new ProcessGroupFlowFileActivity(this);
private FlowFileConcurrency flowFileConcurrency = FlowFileConcurrency.UNBOUNDED;
private volatile FlowFileGate flowFileGate = new UnboundedFlowFileGate();
@@ -295,7 +300,7 @@ public final class StandardProcessGroup implements ProcessGroup {
@Override
public Authorizable getParentAuthorizable() {
- return getParent();
+ return explicitParentAuthorizable == null ? getParent() : explicitParentAuthorizable;
}
@Override
@@ -1720,7 +1725,7 @@ public final class StandardProcessGroup implements ProcessGroup {
}
@Override
- public Future<Void> startProcessor(final ProcessorNode processor, final boolean failIfStopping) {
+ public CompletableFuture<Void> startProcessor(final ProcessorNode processor, final boolean failIfStopping) {
readLock.lock();
try {
if (getProcessor(processor.getIdentifier()) == null) {
@@ -3931,7 +3936,7 @@ public final class StandardProcessGroup implements ProcessGroup {
// because this allows us to find the Controller Service when doing a Flow Diff.
ancestorServiceIds = parentGroup.getControllerServices(true).stream()
.map(cs -> cs.getVersionedComponentId().orElse(
- NiFiRegistryFlowMapper.generateVersionedComponentId(cs.getIdentifier())))
+ VersionedComponentFlowMapper.generateVersionedComponentId(cs.getIdentifier())))
.collect(Collectors.toSet());
}
@@ -3976,7 +3981,7 @@ public final class StandardProcessGroup implements ProcessGroup {
}
try {
- final NiFiRegistryFlowMapper mapper = new NiFiRegistryFlowMapper(extensionManager);
+ final VersionedComponentFlowMapper mapper = new VersionedComponentFlowMapper(extensionManager);
final VersionedProcessGroup versionedGroup = mapper.mapProcessGroup(this, controllerServiceProvider, flowManager, false);
final ComparableDataFlow currentFlow = new StandardComparableDataFlow("Local Flow", versionedGroup);
@@ -4551,6 +4556,48 @@ public final class StandardProcessGroup implements ProcessGroup {
return statelessFlowTimeout;
}
+ @Override
+ public FlowFileActivity getFlowFileActivity() {
+ return flowFileActivity;
+ }
+
+ @Override
+ public void setExplicitParentAuthorizable(final Authorizable parent) {
+ this.explicitParentAuthorizable = parent;
+ }
+
+ @Override
+ public CompletableFuture<Void> purge() {
+ final CompletableFuture<Void> purgeFuture = new CompletableFuture<>();
+
+ final Thread purgeThread = Thread.ofVirtual().name("Purge " + this).start(() -> {
+ try {
+ stopProcessing().get();
+ controllerServiceProvider.disableControllerServicesAsync(getControllerServices(true)).get();
+ purgeQueues();
+ removeComponents(this);
+
+ purgeFuture.complete(null);
+ } catch (final Throwable t) {
+ purgeFuture.completeExceptionally(t);
+ }
+ });
+
+ return purgeFuture;
+ }
+
+ private void purgeQueues() throws ExecutionException, InterruptedException {
+ for (final Connection connection : getConnections()) {
+ final FlowFileQueue flowFileQueue = connection.getFlowFileQueue();
+ if (flowFileQueue.isEmpty()) {
+ continue;
+ }
+
+ final DropFlowFileStatus status = connection.getFlowFileQueue().dropFlowFiles("purge-queues-" + getIdentifier(), "Framework");
+ status.getCompletionFuture().get();
+ }
+ }
+
@Override
public void setStatelessFlowTimeout(final String statelessFlowTimeout) {
if (statelessFlowTimeout == null) {
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ConnectorLogObserver.java
similarity index 72%
copy from nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
copy to nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ConnectorLogObserver.java
index 7209492ca3..d6eba91f98 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ConnectorLogObserver.java
@@ -14,26 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.nifi.logging;
-import org.apache.nifi.controller.ProcessorNode;
+import org.apache.nifi.components.connector.ConnectorNode;
import org.apache.nifi.events.BulletinFactory;
import org.apache.nifi.reporting.BulletinRepository;
import org.apache.nifi.reporting.Severity;
-/**
- * Observes processor log messages and converts them into Bulletins.
- */
-public class ProcessorLogObserver implements LogObserver {
-
+public class ConnectorLogObserver implements LogObserver {
private static final String CATEGORY = "Log Message";
private final BulletinRepository bulletinRepository;
- private final ProcessorNode processorNode;
+ private final ConnectorNode connectorNode;
- public ProcessorLogObserver(BulletinRepository bulletinRepository, ProcessorNode processorNode) {
+ public ConnectorLogObserver(final BulletinRepository bulletinRepository, final ConnectorNode connectorNode) {
this.bulletinRepository = bulletinRepository;
- this.processorNode = processorNode;
+ this.connectorNode = connectorNode;
}
@Override
@@ -41,14 +38,12 @@ public class ProcessorLogObserver implements LogObserver {
// Map LogLevel.WARN to Severity.WARNING so that we are consistent with the Severity enumeration. Else, just use whatever
// the LogLevel is (INFO and ERROR map directly and all others we will just accept as they are).
final String bulletinLevel = (message.getLogLevel() == LogLevel.WARN) ? Severity.WARNING.name() : message.getLogLevel().toString();
- bulletinRepository.addBulletin(
- BulletinFactory.createBulletin(processorNode, CATEGORY, bulletinLevel, message.getMessage(), message.getFlowFileUuid(), message.getThrowable())
- );
+ bulletinRepository.addBulletin(BulletinFactory.createBulletin(connectorNode, CATEGORY, bulletinLevel, message.getMessage()));
}
@Override
public String getComponentDescription() {
- return processorNode.toString();
+ return connectorNode.toString();
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
index 7209492ca3..597b3c8c10 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/ProcessorLogObserver.java
@@ -31,7 +31,7 @@ public class ProcessorLogObserver implements LogObserver {
private final BulletinRepository bulletinRepository;
private final ProcessorNode processorNode;
- public ProcessorLogObserver(BulletinRepository bulletinRepository, ProcessorNode processorNode) {
+ public ProcessorLogObserver(final BulletinRepository bulletinRepository, final ProcessorNode processorNode) {
this.bulletinRepository = bulletinRepository;
this.processorNode = processorNode;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterReferenceManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterReferenceManager.java
index a05d5c57ab..1944f12751 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterReferenceManager.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterReferenceManager.java
@@ -22,7 +22,6 @@ import org.apache.nifi.controller.ComponentNode;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.ProcessorNode;
import org.apache.nifi.controller.PropertyConfiguration;
-import org.apache.nifi.controller.flow.FlowManager;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.groups.ProcessGroup;
@@ -33,14 +32,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
+import java.util.function.Supplier;
public class StandardParameterReferenceManager implements ParameterReferenceManager {
- private final FlowManager flowManager;
+ private final Supplier<ProcessGroup> rootGroupLookup;
// TODO: Consider reworking this so that we don't have to recurse through all components all the time and instead
// have a 'caching' impl that AbstractComponentNode.setProperties() adds to/subtracts from.
- public StandardParameterReferenceManager(final FlowManager flowManager) {
- this.flowManager = flowManager;
+ public StandardParameterReferenceManager(final Supplier<ProcessGroup> rootGroupLookup) {
+ this.rootGroupLookup = rootGroupLookup;
}
@Override
@@ -57,14 +57,14 @@ public class StandardParameterReferenceManager implements ParameterReferenceMana
public List<ParameterReferencedControllerServiceData> getReferencedControllerServiceData(final ParameterContext parameterContext, final String parameterName) {
final List<ParameterReferencedControllerServiceData> referencedControllerServiceData = new ArrayList<>();
+ final ProcessGroup rootGroup = rootGroupLookup.get();
final String versionedServiceId = parameterContext.getParameter(parameterName)
.map(Parameter::getValue)
- .map(this.flowManager::getControllerServiceNode)
+ .map(serviceId -> rootGroup.findControllerService(serviceId, true, true))
.flatMap(VersionedComponent::getVersionedComponentId)
.orElse(null);
- final ProcessGroup rootGroup = flowManager.getRootGroup();
final List<ProcessGroup> referencingGroups = rootGroup.findAllProcessGroups(group -> group.referencesParameterContext(parameterContext));
for (final ProcessGroup group : referencingGroups) {
@@ -90,7 +90,7 @@ public class StandardParameterReferenceManager implements ParameterReferenceMana
@Override
public Set<ProcessGroup> getProcessGroupsBound(final ParameterContext parameterContext) {
- final ProcessGroup rootGroup = flowManager.getRootGroup();
+ final ProcessGroup rootGroup = rootGroupLookup.get();
final List<ProcessGroup> referencingGroups = rootGroup.findAllProcessGroups(group -> group.referencesParameterContext(parameterContext));
return new HashSet<>(referencingGroups);
@@ -100,14 +100,13 @@ public class StandardParameterReferenceManager implements ParameterReferenceMana
final Function<ProcessGroup, Collection<T>> componentFunction) {
final Set<T> referencingComponents = new HashSet<>();
- final ProcessGroup rootGroup = flowManager.getRootGroup();
+ final ProcessGroup rootGroup = rootGroupLookup.get();
final List<ProcessGroup> referencingGroups = rootGroup.findAllProcessGroups(group -> group.referencesParameterContext(parameterContext));
for (final ProcessGroup group : referencingGroups) {
for (final T componentNode : componentFunction.apply(group)) {
if (componentNode.isReferencingParameter(parameterName)) {
referencingComponents.add(componentNode);
- continue;
}
}
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
index 5edcb7c026..f82b8eb24b 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
@@ -37,7 +37,7 @@ import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.expression.ExpressionLanguageCompiler;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.parameter.Parameter;
-import org.apache.nifi.parameter.ParameterContext;
+import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.parameter.ParameterReference;
import java.util.Collection;
@@ -59,7 +59,7 @@ public class StandardValidationContext implements ValidationContext {
private final String annotationData;
private final String groupId;
private final String componentId;
- private final ParameterContext parameterContext;
+ private final ParameterLookup parameterLookup;
private final AtomicReference<Map<PropertyDescriptor, String>> effectiveValuesRef = new AtomicReference<>();
private final boolean validateConnections;
@@ -69,7 +69,7 @@ public class StandardValidationContext implements ValidationContext {
final String annotationData,
final String groupId,
final String componentId,
- final ParameterContext parameterContext,
+ final ParameterLookup parameterLookup,
final boolean validateConnections) {
this.controllerServiceProvider = controllerServiceProvider;
@@ -77,7 +77,7 @@ public class StandardValidationContext implements ValidationContext {
this.annotationData = annotationData;
this.groupId = groupId;
this.componentId = componentId;
- this.parameterContext = parameterContext;
+ this.parameterLookup = parameterLookup;
this.validateConnections = validateConnections;
preparedQueries = new HashMap<>(properties.size());
@@ -85,7 +85,7 @@ public class StandardValidationContext implements ValidationContext {
final PropertyDescriptor desc = entry.getKey();
final PropertyConfiguration configuration = entry.getValue();
- String value = (configuration == null) ? null : configuration.getEffectiveValue(parameterContext);
+ String value = (configuration == null) ? null : configuration.getEffectiveValue(parameterLookup);
if (value == null) {
value = desc.getDefaultValue();
}
@@ -103,12 +103,12 @@ public class StandardValidationContext implements ValidationContext {
@Override
public PropertyValue newPropertyValue(final String rawValue) {
final ResourceContext resourceContext = new StandardResourceContext(new StandardResourceReferenceFactory(), null);
- return new StandardPropertyValue(resourceContext, rawValue, controllerServiceProvider, parameterContext, Query.prepareWithParametersPreEvaluated(rawValue));
+ return new StandardPropertyValue(resourceContext, rawValue, controllerServiceProvider, parameterLookup, Query.prepareWithParametersPreEvaluated(rawValue));
}
@Override
public ExpressionLanguageCompiler newExpressionLanguageCompiler() {
- return new StandardExpressionLanguageCompiler(parameterContext);
+ return new StandardExpressionLanguageCompiler(parameterLookup);
}
@Override
@@ -123,9 +123,9 @@ public class StandardValidationContext implements ValidationContext {
@Override
public PropertyValue getProperty(final PropertyDescriptor property) {
final PropertyConfiguration configuredValue = properties.get(property);
- final String effectiveValue = configuredValue == null ? property.getDefaultValue() : configuredValue.getEffectiveValue(parameterContext);
+ final String effectiveValue = configuredValue == null ? property.getDefaultValue() : configuredValue.getEffectiveValue(parameterLookup);
final ResourceContext resourceContext = new StandardResourceContext(new StandardResourceReferenceFactory(), property);
- return new StandardPropertyValue(resourceContext, effectiveValue, controllerServiceProvider, parameterContext, preparedQueries.get(property));
+ return new StandardPropertyValue(resourceContext, effectiveValue, controllerServiceProvider, parameterLookup, preparedQueries.get(property));
}
@Override
@@ -139,7 +139,7 @@ public class StandardValidationContext implements ValidationContext {
for (final Map.Entry<PropertyDescriptor, PropertyConfiguration> entry : this.properties.entrySet()) {
final PropertyDescriptor descriptor = entry.getKey();
final PropertyConfiguration configuration = entry.getValue();
- final String value = configuration == null ? descriptor.getDefaultValue() : configuration.getEffectiveValue(parameterContext);
+ final String value = configuration == null ? descriptor.getDefaultValue() : configuration.getEffectiveValue(parameterLookup);
valueMap.put(entry.getKey(), value);
}
@@ -215,20 +215,20 @@ public class StandardValidationContext implements ValidationContext {
@Override
public boolean isParameterDefined(final String parameterName) {
- if (parameterContext == null) {
+ if (parameterLookup == null) {
return false;
}
- return parameterContext.getParameter(parameterName).isPresent();
+ return parameterLookup.getParameter(parameterName).isPresent();
}
@Override
public boolean isParameterSet(final String parameterName) {
- if (parameterContext == null) {
+ if (parameterLookup == null) {
return false;
}
- final Optional<Parameter> parameterOption = parameterContext.getParameter(parameterName);
+ final Optional<Parameter> parameterOption = parameterLookup.getParameter(parameterName);
if (!parameterOption.isPresent()) {
return false;
}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
index 00a7b6852a..a1d8178f91 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
@@ -22,7 +22,7 @@ import org.apache.nifi.controller.PropertyConfiguration;
import org.apache.nifi.controller.ValidationContextFactory;
import org.apache.nifi.controller.flowanalysis.FlowAnalyzer;
import org.apache.nifi.controller.service.ControllerServiceProvider;
-import org.apache.nifi.parameter.ParameterContext;
+import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.validation.RuleViolationsManager;
import java.util.Map;
@@ -50,8 +50,8 @@ public class StandardValidationContextFactory implements ValidationContextFactor
@Override
public ValidationContext newValidationContext(final Map<PropertyDescriptor, PropertyConfiguration> properties, final String annotationData, final String groupId, final String componentId,
- final ParameterContext parameterContext, final boolean validateConnections) {
- return new StandardValidationContext(serviceProvider, properties, annotationData, groupId, componentId, parameterContext, validateConnections);
+ final ParameterLookup parameterLookup, final boolean validateConnections) {
+ return new StandardValidationContext(serviceProvider, properties, annotationData, groupId, componentId, parameterLookup, validateConnections);
}
@Override
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNode.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNode.java
index 2fb9cae6fe..14d555d013 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNode.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/registry/flow/FlowAnalyzingRegistryClientNode.java
@@ -44,7 +44,7 @@ import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.parameter.ParameterUpdate;
import org.apache.nifi.registry.flow.mapping.InstantiatedVersionedProcessGroup;
-import org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper;
+import org.apache.nifi.registry.flow.mapping.VersionedComponentFlowMapper;
import org.apache.nifi.validation.RuleViolation;
import org.apache.nifi.validation.RuleViolationsManager;
import org.slf4j.Logger;
@@ -69,7 +69,7 @@ public final class FlowAnalyzingRegistryClientNode implements FlowRegistryClient
... 14802 lines suppressed ...