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>

Reply via email to