Repository: nifi Updated Branches: refs/heads/master 0da4f50ea -> ad80f5f06
NIFI-5598: Allow JMS Processors to lookup Connection Factory via JNDI NIFI-5598: Expose JNDI Principal & Credentails as explicit properties Signed-off-by: Matthew Burgess <mattyb...@apache.org> This closes #3005 Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/ad80f5f0 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/ad80f5f0 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/ad80f5f0 Branch: refs/heads/master Commit: ad80f5f064a2895390bdff77278162a1fba68543 Parents: 0da4f50 Author: Mark Payne <marka...@hotmail.com> Authored: Fri Sep 14 10:48:59 2018 -0400 Committer: Matthew Burgess <mattyb...@apache.org> Committed: Wed Sep 19 12:56:58 2018 -0400 ---------------------------------------------------------------------- .../cf/JndiJmsConnectionFactoryProvider.java | 194 +++++++++++++++++++ ...org.apache.nifi.controller.ControllerService | 2 + .../additionalDetails.html | 94 +++++++++ 3 files changed, 290 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/ad80f5f0/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JndiJmsConnectionFactoryProvider.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JndiJmsConnectionFactoryProvider.java b/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JndiJmsConnectionFactoryProvider.java new file mode 100644 index 0000000..876d933 --- /dev/null +++ b/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JndiJmsConnectionFactoryProvider.java @@ -0,0 +1,194 @@ +/* + * 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.jms.cf; + +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnDisabled; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.PropertyDescriptor.Builder; +import org.apache.nifi.components.Validator; +import org.apache.nifi.controller.AbstractControllerService; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.expression.ExpressionLanguageScope; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.util.StandardValidators; + +import javax.jms.ConnectionFactory; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + +import static org.apache.nifi.processor.util.StandardValidators.NON_EMPTY_VALIDATOR; + +@Tags({"jms", "jndi", "messaging", "integration", "queue", "topic", "publish", "subscribe"}) +@CapabilityDescription("Provides a service to lookup an existing JMS ConnectionFactory using the Java Naming and Directory Interface (JNDI).") +@DynamicProperty( + description = "In order to perform a JNDI Lookup, an Initial Context must be established. When this is done, an Environment can be established for the context. Any dynamic/user-defined property" + + " that is added to this Controller Service will be added as an Environment configuration/variable to this Context.", + name = "The name of a JNDI Initial Context environment variable.", + value = "The value of the JNDI Initial Context Environment variable.", + expressionLanguageScope = ExpressionLanguageScope.VARIABLE_REGISTRY) +@SeeAlso(classNames = {"org.apache.nifi.jms.processors.ConsumeJMS", "org.apache.nifi.jms.processors.PublishJMS", "org.apache.nifi.jms.cf.JMSConnectionFactoryProvider"}) +public class JndiJmsConnectionFactoryProvider extends AbstractControllerService implements JMSConnectionFactoryProviderDefinition{ + + static final PropertyDescriptor INITIAL_NAMING_FACTORY_CLASS = new Builder() + .name("java.naming.factory.initial") + .displayName("Initial Naming Factory Class") + .description("The fully qualified class name of the Java Initial Naming Factory (java.naming.factory.initial).") + .addValidator(NON_EMPTY_VALIDATOR) + .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY) + .required(true) + .build(); + static final PropertyDescriptor NAMING_PROVIDER_URL = new Builder() + .name("java.naming.provider.url") + .displayName("Naming Provider URL") + .description("The URL of the JNDI Naming Provider to use") + .required(true) + .addValidator(NON_EMPTY_VALIDATOR) + .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY) + .build(); + static final PropertyDescriptor CONNECTION_FACTORY_NAME = new Builder() + .name("connection.factory.name") + .displayName("Connection Factory Name") + .description("The name of the JNDI Object to lookup for the Connection Factory") + .required(true) + .addValidator(NON_EMPTY_VALIDATOR) + .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY) + .build(); + static final PropertyDescriptor NAMING_FACTORY_LIBRARIES = new Builder() + .name("naming.factory.libraries") + .displayName("Naming Factory Libraries") + .description("Specifies .jar files or directories to add to the ClassPath in order to find the Initial Naming Factory Class") + .required(false) + .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY) + .addValidator(StandardValidators.createListValidator(true, true, StandardValidators.createURLorFileValidator())) + .dynamicallyModifiesClasspath(true) + .build(); + static final PropertyDescriptor PRINCIPAL = new Builder() + .name("java.naming.security.principal") + .displayName("JNDI Principal") + .description("The Principal to use when authenticating with JNDI") + .required(false) + .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + static final PropertyDescriptor CREDENTIALS = new Builder() + .name("java.naming.security.credentials") + .displayName("Credentials") + .description("The Credentials to use when authenticating with JNDI") + .required(false) + .expressionLanguageSupported(ExpressionLanguageScope.NONE) + .addValidator(Validator.VALID) + .sensitive(true) + .build(); + + private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = Arrays.asList( + INITIAL_NAMING_FACTORY_CLASS, + NAMING_PROVIDER_URL, + CONNECTION_FACTORY_NAME, + NAMING_FACTORY_LIBRARIES, + PRINCIPAL, + CREDENTIALS); + + private ConnectionFactory connectionFactory; + + @Override + protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { + return PROPERTY_DESCRIPTORS; + } + + @Override + protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) { + return new Builder() + .name(propertyDescriptorName) + .displayName(propertyDescriptorName) + .description("JNDI Initial Context Environment configuration for '" + propertyDescriptorName + "'") + .required(false) + .dynamic(true) + .addValidator(Validator.VALID) + .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY) + .build(); + } + + @OnDisabled + public void shutdown() { + connectionFactory = null; + } + + @Override + public synchronized ConnectionFactory getConnectionFactory() { + if (connectionFactory == null) { + connectionFactory = lookupConnectionFactory(); + } else { + getLogger().debug("Connection Factory has already been obtained from JNDI. Will return cached instance."); + } + + return connectionFactory; + } + + private ConnectionFactory lookupConnectionFactory() { + try { + final ConfigurationContext context = getConfigurationContext(); + + final Hashtable<String, String> env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, context.getProperty(INITIAL_NAMING_FACTORY_CLASS).evaluateAttributeExpressions().getValue().trim()); + env.put(Context.PROVIDER_URL, context.getProperty(NAMING_PROVIDER_URL).evaluateAttributeExpressions().getValue().trim()); + + final String principal = context.getProperty(PRINCIPAL).evaluateAttributeExpressions().getValue(); + if (principal != null) { + env.put(Context.SECURITY_PRINCIPAL, principal); + } + + final String credentials = context.getProperty(CREDENTIALS).getValue(); + if (credentials != null) { + env.put(Context.SECURITY_CREDENTIALS, credentials); + } + + context.getProperties().keySet().forEach(descriptor -> { + if (descriptor.isDynamic()) { + env.put(descriptor.getName(), context.getProperty(descriptor).evaluateAttributeExpressions().getValue()); + } + }); + + final String factoryName = context.getProperty(CONNECTION_FACTORY_NAME).evaluateAttributeExpressions().getValue().trim(); + getLogger().debug("Looking up Connection Factory with name [{}] using JNDI Environment {}", new Object[] {factoryName, env}); + + final Context initialContext = new InitialContext(env); + final Object factoryObject = initialContext.lookup(factoryName); + + getLogger().debug("Obtained {} from JNDI", new Object[] {factoryObject}); + + if (factoryObject == null) { + throw new ProcessException("Got a null Factory Object from JNDI"); + } + if (!(factoryObject instanceof ConnectionFactory)) { + throw new ProcessException("Successfully performed JNDI lookup with Object Name [" + factoryName + "] but the returned object is not a ConnectionFactory. " + + "Instead, is of type " + factoryObject.getClass() + " : " + factoryObject); + } + + return (ConnectionFactory) factoryObject; + } catch (final NamingException ne) { + throw new ProcessException("Could not obtain JMS Connection Factory from JNDI", ne); + } + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/ad80f5f0/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService index f191675..95a1ae7 100644 --- a/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService +++ b/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService @@ -12,4 +12,6 @@ # 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.jms.cf.JMSConnectionFactoryProvider +org.apache.nifi.jms.cf.JndiJmsConnectionFactoryProvider http://git-wip-us.apache.org/repos/asf/nifi/blob/ad80f5f0/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/docs/org.apache.nifi.jms.cf.JndiJmsConnectionFactoryProvider/additionalDetails.html ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/docs/org.apache.nifi.jms.cf.JndiJmsConnectionFactoryProvider/additionalDetails.html b/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/docs/org.apache.nifi.jms.cf.JndiJmsConnectionFactoryProvider/additionalDetails.html new file mode 100644 index 0000000..b30720b --- /dev/null +++ b/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/resources/docs/org.apache.nifi.jms.cf.JndiJmsConnectionFactoryProvider/additionalDetails.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<html lang="en"> +<!-- + 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. +--> +<head> + <meta charset="utf-8" /> + <title>JMSConnectionFactoryProvider</title> + <link rel="stylesheet" href="../../../../../css/component-usage.css" type="text/css" /> +</head> + +<body> +<h2>Description:</h2> +<p> + This ControllerService allows users to reference a JMS Connection Factory that has already been established and + made available via Java Naming and Directory Interface (JNDI) Server. Please see documentation from your JMS Vendor in order + to understand the appropriate values to configure for this service. +</p> +<p> + A Connection Factory in Java is typically obtained via JNDI in code like below. The comments have been added in to explain how + this maps to the Controller Service's configuration. +</p> +<pre> +<code> +Hashtable env = new Hashtable(); +env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_NAMING_FACTORY_CLASS); // Value for this comes from the "Initial Naming Factory Class" property. +env.put(Context.PROVIDER_URL, NAMING_PROVIDER_URL); // Value for this comes from the "Naming Provider URL" property. +env.put("My-Environment-Variable", "Environment-Variable-Value"); // This is accomplished by added a user-defined property with name "My-Environment-Variable" and value "Environment-Variable-Value" + +Context initialContext = new InitialContext(env); +ConnectionFactory connectionFactory = initialContext.lookup(CONNECTION_FACTORY_NAME); // Value for Connection Factory name comes from "Connection Factory Name" property +</code> +</pre> + +<p> + It is also important to note that, in order for this to work, the class named by the Initial Naming Factory Class must be available on the classpath. + In NiFi, this is accomplished by setting the "Naming Factory Libraries" property to point to one or more .jar files or directories (comma-separated values). +</p> + +<p> + When the Controller Service is disabled and then re-enabled, it will perform the JNDI lookup again. Once the Connection Factory has been obtained, though, + it will not perform another JNDI lookup until the service is disabled. +</p> + + +<h2>Example:</h2> + +<p> + As an example, the following configuration may be used to connect to Active MQ's JMS Broker, using the Connection Factory provided via their embedded JNDI server: +</p> + +<table> + <thead> + <th>Property Name</th> + <th>Property Value</th> + </thead> + <tbody> + <tr> + <td>Initial Naming Factory Class</td> + <td>org.apache.activemq.jndi.ActiveMQInitialContextFactory</td> + </tr> + <tr> + <td>Naming Provider URL</td> + <td>tcp://jms-broker:61616</td> + </tr> + <tr> + <td>Connection Factory Name</td> + <td>ConnectionFactory</td> + </tr> + <tr> + <td>Naming Factory Libraries</td> + <td>/opt/apache-activemq-5.15.2/lib/</td> + </tr> + </tbody> +</table> + +<p> + The above example assumes that there exists as host that is accessible with hostname "jms-broker" and that is running Apache ActiveMQ on port 61616 and also that + the jar containing the org.apache.activemq.jndi.ActiveMQInitialContextFactory class can be found within the /opt/apache-activemq-5.15.2/lib/ directory. +</p> + +</body> +</html>