This is an automated email from the ASF dual-hosted git repository.

sai_boorlagadda pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 8354b61  GEODE-3875: gfsh command to create jndi binding (#1475)
8354b61 is described below

commit 8354b61afb363db3eea44e31a496c1a4372e1d60
Author: Sai Boorlagadda <sai.boorlaga...@gmail.com>
AuthorDate: Fri Feb 23 06:15:16 2018 -0800

    GEODE-3875: gfsh command to create jndi binding (#1475)
---
 .../internal/datasource/AbstractPoolCache.java     |  11 +-
 .../datasource/ConfiguredDataSourceProperties.java |  11 +-
 .../internal/datasource/ManagedPoolCacheImpl.java  |   2 +-
 .../internal/datasource/TranxPoolCacheImpl.java    |   2 +-
 .../apache/geode/internal/jndi/JNDIInvoker.java    |   9 +-
 .../cli/commands/CommandAvailabilityIndicator.java |   3 +-
 .../cli/commands/CreateJndiBindingCommand.java     | 258 +++++++++++++
 .../cli/converters/ConfigPropertyConverter.java    |  58 +++
 .../cli/functions/CreateJndiBindingFunction.java   |  38 ++
 .../cli/functions/JndiBindingConfiguration.java    | 217 +++++++++++
 .../configuration/domain/Configuration.java        |  13 +-
 .../sanctioned-geode-core-serializables.txt        |   3 +
 .../CreateAsyncEventQueueCommandDUnitTest.java     |   3 +-
 .../CreateDefinedIndexesCommandDUnitTest.java      |   5 +-
 .../CreateJndiBindingCommandDUnitTest.java         | 106 ++++++
 .../cli/commands/CreateJndiBindingCommandTest.java | 408 +++++++++++++++++++++
 .../converters/ConfigPropertyConverterTest.java    |  75 ++++
 17 files changed, 1197 insertions(+), 25 deletions(-)

diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java
 
b/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java
index 2af7f47..ff1ba43 100644
--- 
a/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/datasource/AbstractPoolCache.java
@@ -39,11 +39,6 @@ import org.apache.geode.internal.logging.LoggingThreadGroup;
  * connection pools. The class also implements the Serializable interface. The 
pool maintain a list
  * for keeping the available connections(not assigned to user) and the active 
connections(assigned
  * to user) This is a thread safe class.
- *
- * Second Version .Modified the synchronization code & objects on which locks 
were being taken.
- * Changed the logic of retrieval of connection & returning of connection. The 
beahviour of cleaner
- * thread has been modified such that it waits on activeCache if it is empty. 
Prevention of
- * deadlocks & optmization of code.
  */
 public abstract class AbstractPoolCache implements ConnectionPoolCache, 
Serializable {
 
@@ -56,11 +51,9 @@ public abstract class AbstractPoolCache implements 
ConnectionPoolCache, Serializ
   protected EventListener connEventListner;
   // private String error = "";
   protected ConfiguredDataSourceProperties configProps;
-  // Asif:expirationTime is for the available
-  // connection which are expired in milliseconds
+  // expirationTime is for the available connection which are expired in 
milliseconds
   protected int expirationTime;
-  // Asif:timeOut is for the Active connection which are time out in
-  // milliseconds
+  // timeOut is for the Active connection which are time out in milliseconds
   protected int timeOut;
   // Client Timeout in milliseconds
   protected int loginTimeOut;
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/datasource/ConfiguredDataSourceProperties.java
 
b/geode-core/src/main/java/org/apache/geode/internal/datasource/ConfiguredDataSourceProperties.java
index c947de8..e036f39 100644
--- 
a/geode-core/src/main/java/org/apache/geode/internal/datasource/ConfiguredDataSourceProperties.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/datasource/ConfiguredDataSourceProperties.java
@@ -14,17 +14,18 @@
  */
 package org.apache.geode.internal.datasource;
 
+import java.io.PrintWriter;
+import java.io.Serializable;
+
 /**
- * JavaBean for datasource and poold properties.
+ * JavaBean for datasource and pooled properties.
  *
- * This class now contains only those paramaters which are needed by the 
Gemfire DataSource
- * configuration. This maps to those paramaters which are specified as 
attributes of
+ * This class now contains only those parameters which are needed by the 
Gemfire DataSource
+ * configuration. This maps to those parameters which are specified as 
attributes of
  * <jndi-binding>tag. Those parameters which are specified as attributes of 
<property>tag are not
  * stored.
  *
  */
-import java.io.*;
-
 public class ConfiguredDataSourceProperties implements Serializable {
 
   private static final long serialVersionUID = 1241739895646314739L;
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/datasource/ManagedPoolCacheImpl.java
 
b/geode-core/src/main/java/org/apache/geode/internal/datasource/ManagedPoolCacheImpl.java
index 8702f70..f3cf49b 100755
--- 
a/geode-core/src/main/java/org/apache/geode/internal/datasource/ManagedPoolCacheImpl.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/datasource/ManagedPoolCacheImpl.java
@@ -30,7 +30,7 @@ import org.apache.geode.internal.logging.LogService;
 
 /**
  * This class implements a connection pool for Managed connection. Extends the 
AbstractPoolCache to
- * inherit the pool bahavior.
+ * inherit the pool behavior.
  *
  */
 public class ManagedPoolCacheImpl extends AbstractPoolCache {
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/datasource/TranxPoolCacheImpl.java
 
b/geode-core/src/main/java/org/apache/geode/internal/datasource/TranxPoolCacheImpl.java
index 9a59aeb..c577471 100644
--- 
a/geode-core/src/main/java/org/apache/geode/internal/datasource/TranxPoolCacheImpl.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/datasource/TranxPoolCacheImpl.java
@@ -29,7 +29,7 @@ import org.apache.geode.internal.logging.LogService;
 
 /**
  * This class models a connection pool for transactional database connection. 
Extends the
- * AbstractPoolCache to inherit the pool bahavior.
+ * AbstractPoolCache to inherit the pool behavior.
  *
  */
 public class TranxPoolCacheImpl extends AbstractPoolCache {
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/jndi/JNDIInvoker.java 
b/geode-core/src/main/java/org/apache/geode/internal/jndi/JNDIInvoker.java
index 73377b9..99898c5 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/jndi/JNDIInvoker.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/jndi/JNDIInvoker.java
@@ -330,6 +330,7 @@ public class JNDIInvoker {
       } else if (value.equals("SimpleDataSource")) {
         ds = DataSourceFactory.getSimpleDataSource(map, props);
         ctx.rebind("java:/" + jndiName, ds);
+        dataSourceList.add(ds);
         if (writer.fineEnabled())
           writer.fine("Bound java:/" + jndiName + " to Context");
       } else if (value.equals("ManagedDataSource")) {
@@ -374,8 +375,8 @@ public class JNDIInvoker {
   public static TransactionManager getTransactionManager() {
     return transactionManager;
   }
-  // try to find websphere lookups since we came here
-  /*
-   * private static void print(String str) { if (DEBUG) { 
System.err.println(str); } }
-   */
+
+  public static int getNoOfAvailableDataSources() {
+    return dataSourceList.size();
+  }
 }
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CommandAvailabilityIndicator.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CommandAvailabilityIndicator.java
index cfdeaa5..f6939f3 100644
--- 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CommandAvailabilityIndicator.java
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CommandAvailabilityIndicator.java
@@ -49,7 +49,8 @@ public class CommandAvailabilityIndicator implements 
CommandMarker {
       CliStrings.CREATE_GATEWAYRECEIVER, CliStrings.START_GATEWAYRECEIVER,
       CliStrings.STOP_GATEWAYRECEIVER, CliStrings.LIST_GATEWAY, 
CliStrings.STATUS_GATEWAYSENDER,
       CliStrings.STATUS_GATEWAYRECEIVER, CliStrings.LOAD_BALANCE_GATEWAYSENDER,
-      CliStrings.DESTROY_GATEWAYSENDER, 
AlterAsyncEventQueueCommand.COMMAND_NAME})
+      CliStrings.DESTROY_GATEWAYSENDER, 
AlterAsyncEventQueueCommand.COMMAND_NAME,
+      CreateJndiBindingCommand.CREATE_JNDIBINDING})
   public boolean clientCommandsAvailable() {
     Gfsh gfsh = Gfsh.getCurrentInstance();
 
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommand.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommand.java
new file mode 100644
index 0000000..19abfd9
--- /dev/null
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommand.java
@@ -0,0 +1,258 @@
+/*
+ * 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.geode.management.internal.cli.commands;
+
+import static 
org.apache.geode.management.internal.cli.result.ResultBuilder.buildResult;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+import org.apache.logging.log4j.Logger;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.distributed.internal.ClusterConfigurationService;
+import org.apache.geode.internal.datasource.ConfigProperty;
+import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.Result;
+import 
org.apache.geode.management.internal.cli.exceptions.EntityExistsException;
+import org.apache.geode.management.internal.cli.functions.CliFunctionResult;
+import 
org.apache.geode.management.internal.cli.functions.CreateJndiBindingFunction;
+import 
org.apache.geode.management.internal.cli.functions.JndiBindingConfiguration;
+import org.apache.geode.management.internal.cli.i18n.CliStrings;
+import org.apache.geode.management.internal.cli.result.ResultBuilder;
+import org.apache.geode.management.internal.configuration.domain.Configuration;
+import org.apache.geode.management.internal.configuration.utils.XmlUtils;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class CreateJndiBindingCommand implements GfshCommand {
+  private static final Logger logger = LogService.getLogger();
+
+  static final String CREATE_JNDIBINDING = "create jndi-binding";
+  static final String CREATE_JNDIBINDING__HELP =
+      "Create a jndi binding that holds the configuration for the XA 
datasource.";
+  static final String BLOCKING_TIMEOUT_SECONDS = "blocking-timeout-seconds";
+  static final String BLOCKING_TIMEOUT_SECONDS__HELP =
+      "This element specifies the maximum time to block while waiting for a 
connection before throwing an exception.";
+  static final String CONNECTION_POOLED_DATASOURCE_CLASS = 
"conn-pooled-datasource-class";
+  static final String CONNECTION_POOLED_DATASOURCE_CLASS__HELP =
+      "This is the fully qualified name of the connection pool implementation 
to hold XS datasource connections.";
+  static final String CONNECTION_URL = "connection-url";
+  static final String CONNECTION_URL__HELP =
+      "This is the JDBC driver connection URL string, for example, 
jdbc:hsqldb:hsql://localhost:1701.";
+  static final String IDLE_TIMEOUT_SECONDS = "idle-timeout-seconds";
+  static final String IDLE_TIMEOUT_SECONDS__HELP =
+      "This element specifies the time a connection may be idle before being 
closed.";
+  static final String INIT_POOL_SIZE = "init-pool-size";
+  static final String INIT_POOL_SIZE__HELP =
+      "This element specifies the initial number of connections the pool 
should hold.";
+  static final String JDBC_DRIVER_CLASS = "jdbc-driver-class";
+  static final String JDBC_DRIVER_CLASS__HELP =
+      "This is the fully qualified name of the JDBC driver class.";
+  static final String JNDI_NAME = "name";
+  static final String JNDI_NAME__HELP = "Name of the binding to be created.";
+  static final String LOGIN_TIMEOUT_SECONDS = "login-timeout-seconds";
+  static final String LOGIN_TIMEOUT_SECONDS__HELP =
+      "Time in seconds after which the client thread for retrieving connection 
will experience timeout.";
+  static final String MANAGED_CONN_FACTORY_CLASS = 
"managed-conn-factory-class";
+  static final String MANAGED_CONN_FACTORY_CLASS__HELP =
+      "This is the fully qualified name of the connection factory 
implementation.";
+  static final String MAX_POOL_SIZE = "max-pool-size";
+  static final String MAX_POOL_SIZE__HELP =
+      "This element specifies the maximum number of connections for a pool. No 
more than the max-pool-size number of connections will be created in a pool.";
+  static final String PASSWORD = "password";
+  static final String PASSWORD__HELP =
+      "This element specifies the default password used when creating a new 
connection.";
+  static final String TRANSACTION_TYPE = "transaction-type";
+  static final String TRANSACTION_TYPE__HELP = "Type of the transaction.";
+  static final String TYPE = "type";
+  static final String TYPE__HELP =
+      "Type of the XA datasource. Type of region to create. The following 
types are pre-defined by the product: MANAGED, SIMPLE, POOLED, XAPOOLED.";
+  static final String USERNAME = "username";
+  static final String USERNAME__HELP =
+      "This element specifies the default username used when creating a new 
connection.";
+  static final String XA_DATASOURCE_CLASS = "xa-datasource-class";
+  static final String XA_DATASOURCE_CLASS__HELP =
+      "The fully qualified name of the javax.sql.XADataSource implementation 
class.";
+  static final String IFNOTEXISTS__HELP =
+      "Skip the create operation when a Jndi binding with the same name 
already exists. The default is to overwrite the entry (false).";
+  static final String DATASOURCE_CONFIG_PROPERTIES = 
"datasource-config-properties";
+  static final String DATASOURCE_CONFIG_PROPERTIES_HELP =
+      "Properties for the custom XSDataSource driver. Append json string 
containing (name, type, value) to set any property. Eg: 
--datasource-config-properties={'name':'name1','type':'type1','value':'value1'},{'name':'name2','type':'type2','value':'value2'}";
+
+  @CliCommand(value = CREATE_JNDIBINDING, help = CREATE_JNDIBINDING__HELP)
+  @CliMetaData(relatedTopic = CliStrings.TOPIC_GEODE_REGION)
+  @ResourceOperation(resource = ResourcePermission.Resource.CLUSTER,
+      operation = ResourcePermission.Operation.MANAGE)
+  public Result createJDNIBinding(
+      @CliOption(key = BLOCKING_TIMEOUT_SECONDS,
+          help = BLOCKING_TIMEOUT_SECONDS__HELP) Integer blockingTimeout,
+      @CliOption(key = CONNECTION_POOLED_DATASOURCE_CLASS,
+          help = CONNECTION_POOLED_DATASOURCE_CLASS__HELP) String 
connectionPooledDatasource,
+      @CliOption(key = CONNECTION_URL, mandatory = true,
+          help = CONNECTION_URL__HELP) String connectionUrl,
+      @CliOption(key = IDLE_TIMEOUT_SECONDS, help = 
IDLE_TIMEOUT_SECONDS__HELP) Integer idleTimeout,
+      @CliOption(key = INIT_POOL_SIZE, help = INIT_POOL_SIZE__HELP) Integer 
initPoolSize,
+      @CliOption(key = JDBC_DRIVER_CLASS, mandatory = true,
+          help = JDBC_DRIVER_CLASS__HELP) String jdbcDriver,
+      @CliOption(key = JNDI_NAME, mandatory = true, help = JNDI_NAME__HELP) 
String jndiName,
+      @CliOption(key = LOGIN_TIMEOUT_SECONDS,
+          help = LOGIN_TIMEOUT_SECONDS__HELP) Integer loginTimeout,
+      @CliOption(key = MANAGED_CONN_FACTORY_CLASS,
+          help = MANAGED_CONN_FACTORY_CLASS__HELP) String managedConnFactory,
+      @CliOption(key = MAX_POOL_SIZE, help = MAX_POOL_SIZE__HELP) Integer 
maxPoolSize,
+      @CliOption(key = PASSWORD, help = PASSWORD__HELP) String password,
+      @CliOption(key = TRANSACTION_TYPE, help = TRANSACTION_TYPE__HELP) String 
transactionType,
+      @CliOption(key = TYPE, mandatory = true,
+          help = TYPE__HELP) JndiBindingConfiguration.DATASOURCE_TYPE type,
+      @CliOption(key = USERNAME, help = USERNAME__HELP) String username,
+      @CliOption(key = XA_DATASOURCE_CLASS, help = XA_DATASOURCE_CLASS__HELP) 
String xaDataSource,
+      @CliOption(key = CliStrings.IFNOTEXISTS, help = IFNOTEXISTS__HELP,
+          specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") 
boolean ifNotExists,
+      @CliOption(key = DATASOURCE_CONFIG_PROPERTIES, optionContext = 
"splittingRegex=,(?![^{]*\\})",
+          help = DATASOURCE_CONFIG_PROPERTIES_HELP) ConfigProperty[] 
dsConfigProperties)
+      throws IOException, SAXException, ParserConfigurationException, 
TransformerException {
+
+    JndiBindingConfiguration configuration = new JndiBindingConfiguration();
+    configuration.setBlockingTimeout(blockingTimeout);
+    configuration.setConnectionPoolDatasource(connectionPooledDatasource);
+    configuration.setConnectionUrl(connectionUrl);
+    configuration.setIdleTimeout(idleTimeout);
+    configuration.setInitPoolSize(initPoolSize);
+    configuration.setJdbcDriver(jdbcDriver);
+    configuration.setJndiName(jndiName);
+    configuration.setLoginTimeout(loginTimeout);
+    configuration.setManagedConnFactory(managedConnFactory);
+    configuration.setMaxPoolSize(maxPoolSize);
+    configuration.setPassword(password);
+    configuration.setTransactionType(transactionType);
+    configuration.setType(type);
+    configuration.setUsername(username);
+    configuration.setXaDatasource(xaDataSource);
+    if (dsConfigProperties != null && dsConfigProperties.length > 0)
+      
configuration.setDatasourceConfigurations(Arrays.asList(dsConfigProperties));
+
+    Result result;
+    boolean persisted = false;
+    ClusterConfigurationService service = getSharedConfiguration();
+
+    if (service != null) {
+      if (isBindingAlreadyExists(jndiName))
+        throw new EntityExistsException(
+            CliStrings.format("Jndi binding with jndi-name \"{0}\" already 
exists.", jndiName),
+            ifNotExists);
+      updateXml(configuration);
+      persisted = true;
+    }
+
+    Set<DistributedMember> targetMembers = findMembers(null, null);
+    if (targetMembers.size() > 0) {
+      List<CliFunctionResult> jndiCreationResult = executeAndGetFunctionResult(
+          new CreateJndiBindingFunction(), configuration, targetMembers);
+      result = buildResult(jndiCreationResult);
+    } else {
+      if (persisted)
+        result =
+            ResultBuilder.createInfoResult("No members found. Cluster 
configuration is updated.");
+      else
+        result = ResultBuilder.createInfoResult("No members found.");
+    }
+
+    result.setCommandPersisted(persisted);
+
+    return result;
+  }
+
+  boolean isBindingAlreadyExists(String jndiName)
+      throws IOException, SAXException, ParserConfigurationException {
+
+    Configuration config = 
getSharedConfiguration().getConfiguration("cluster");
+
+    Document document = 
XmlUtils.createDocumentFromXml(config.getCacheXmlContent());
+    NodeList jndiBindings = document.getElementsByTagName("jndi-binding");
+
+    if (jndiBindings == null || jndiBindings.getLength() == 0) {
+      return false;
+    } else {
+      for (int i = 0; i < jndiBindings.getLength(); i++) {
+        Element eachBinding = (Element) jndiBindings.item(i);
+        if (eachBinding.getAttribute("jndi-name").equals(jndiName))
+          return true;
+      }
+    }
+    return false;
+  }
+
+  void updateXml(JndiBindingConfiguration configuration)
+      throws TransformerException, IOException, SAXException, 
ParserConfigurationException {
+    // cluster group config should always be present
+    Configuration config = 
getSharedConfiguration().getConfiguration("cluster");
+
+    Document document = 
XmlUtils.createDocumentFromXml(config.getCacheXmlContent());
+    Node cacheNode = document.getElementsByTagName("cache").item(0);
+
+    NodeList jndiBindingsNode = document.getElementsByTagName("jndi-bindings");
+
+    if (jndiBindingsNode == null || jndiBindingsNode.getLength() == 0) {
+      Element jndiBindings = document.createElement("jndi-bindings");
+      cacheNode.appendChild(jndiBindings);
+    }
+
+    Element jndiBinding = document.createElement("jndi-binding");
+    jndiBindingsNode.item(0).appendChild(jndiBinding);
+
+    for (Object key : configuration.getParamsAsMap().keySet()) {
+      if (configuration.getParamsAsMap().get(key) != null)
+        jndiBinding.setAttribute(key.toString(),
+            configuration.getParamsAsMap().get(key).toString());
+    }
+
+    for (ConfigProperty configProperty : 
configuration.getDatasourceConfigurations()) {
+      Element configPropertyElement = 
document.createElement("config-property");
+      jndiBinding.appendChild(configPropertyElement);
+
+      Node configPropertyName = document.createElement("config-property-name");
+      configPropertyName.setTextContent(configProperty.getName());
+
+      Node configPropertyType = document.createElement("config-property-type");
+      configPropertyType.setTextContent(configProperty.getType());
+
+      Node configPropertyValue = 
document.createElement("config-property-value");
+      configPropertyValue.setTextContent(configProperty.getValue());
+
+      configPropertyElement.appendChild(configPropertyName);
+      configPropertyElement.appendChild(configPropertyType);
+      configPropertyElement.appendChild(configPropertyValue);
+    }
+
+    String newXml = XmlUtils.prettyXml(document.getFirstChild());
+    config.setCacheXmlContent(newXml);
+
+    getSharedConfiguration().getConfigurationRegion().put("cluster", config);
+  }
+}
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverter.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverter.java
new file mode 100644
index 0000000..1f8219c
--- /dev/null
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.geode.management.internal.cli.converters;
+
+import java.io.IOException;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.shell.core.Completion;
+import org.springframework.shell.core.Converter;
+import org.springframework.shell.core.MethodTarget;
+
+import org.apache.geode.internal.datasource.ConfigProperty;
+
+/***
+ * Added converter to enable auto-completion for index-type
+ *
+ */
+public class ConfigPropertyConverter implements Converter<ConfigProperty> {
+
+  private static ObjectMapper mapper = new ObjectMapper();
+  static {
+    mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+  }
+
+  @Override
+  public boolean supports(Class<?> type, String optionContext) {
+    return ConfigProperty.class.isAssignableFrom(type);
+  }
+
+  @Override
+  public ConfigProperty convertFromText(String value, Class<?> targetType, 
String optionContext) {
+    try {
+      return mapper.readValue(value, ConfigProperty.class);
+    } catch (IOException e) {
+      throw new IllegalArgumentException("invalid json: " + value);
+    }
+  }
+
+  @Override
+  public boolean getAllPossibleValues(List<Completion> completions, Class<?> 
targetType,
+      String existingData, String optionContext, MethodTarget target) {
+    return false;
+  }
+}
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java
new file mode 100644
index 0000000..133acf7
--- /dev/null
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java
@@ -0,0 +1,38 @@
+/*
+ * 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.geode.management.internal.cli.functions;
+
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.cache.execute.ResultSender;
+import org.apache.geode.internal.cache.execute.InternalFunction;
+import org.apache.geode.internal.jndi.JNDIInvoker;
+import org.apache.geode.management.internal.cli.i18n.CliStrings;
+
+public class CreateJndiBindingFunction implements 
InternalFunction<JndiBindingConfiguration> {
+
+  static final String RESULT_MESSAGE =
+      "Initiated jndi binding \"{0}\" on \"{1}\". See server logs to verify.";
+
+  @Override
+  public void execute(FunctionContext<JndiBindingConfiguration> context) {
+    ResultSender<Object> resultSender = context.getResultSender();
+    JndiBindingConfiguration configuration = context.getArguments();
+    JNDIInvoker.mapDatasource(configuration.getParamsAsMap(),
+        configuration.getDatasourceConfigurations());
+
+    resultSender.lastResult(new CliFunctionResult(context.getMemberName(), 
true,
+        CliStrings.format(RESULT_MESSAGE, configuration.getJndiName(), 
context.getMemberName())));
+  }
+}
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration.java
new file mode 100644
index 0000000..316ca74
--- /dev/null
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration.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.geode.management.internal.cli.functions;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.geode.internal.datasource.ConfigProperty;
+
+public class JndiBindingConfiguration implements Serializable {
+
+  public enum DATASOURCE_TYPE {
+    MANAGED("ManagedDataSource"),
+    SIMPLE("SimpleDataSource"),
+    POOLED("PooledDataSource"),
+    XAPOOLED("XAPooledDataSource");
+
+    private final String type;
+
+    DATASOURCE_TYPE(String type) {
+      this.type = type;
+    }
+
+    public String getType() {
+      return this.type;
+    }
+
+    public String getName() {
+      return name();
+    }
+  }
+
+  private Integer blockingTimeout;
+  private String connectionPoolDatasource;
+  private String connectionUrl;
+  private Integer idleTimeout;
+  private Integer initPoolSize;
+  private String jdbcDriver;
+  private String jndiName;
+  private Integer loginTimeout;
+  private String managedConnFactory;
+  private Integer maxPoolSize;
+  private String password;
+  private String transactionType;
+  private DATASOURCE_TYPE type;
+  private String username;
+  private String xaDatasource;
+
+  private List<ConfigProperty> datasourceConfigurations;
+
+  public JndiBindingConfiguration() {
+    datasourceConfigurations = new ArrayList<>();
+  }
+
+  public Integer getBlockingTimeout() {
+    return blockingTimeout;
+  }
+
+  public void setBlockingTimeout(Integer blockingTimeout) {
+    this.blockingTimeout = blockingTimeout;
+  }
+
+  public String getConnectionPoolDatasource() {
+    return connectionPoolDatasource;
+  }
+
+  public void setConnectionPoolDatasource(String connectionPoolDatasource) {
+    this.connectionPoolDatasource = connectionPoolDatasource;
+  }
+
+  public String getConnectionUrl() {
+    return connectionUrl;
+  }
+
+  public void setConnectionUrl(String connectionUrl) {
+    this.connectionUrl = connectionUrl;
+  }
+
+  public Integer getIdleTimeout() {
+    return idleTimeout;
+  }
+
+  public void setIdleTimeout(Integer idleTimeout) {
+    this.idleTimeout = idleTimeout;
+  }
+
+  public Integer getInitPoolSize() {
+    return initPoolSize;
+  }
+
+  public void setInitPoolSize(Integer initPoolSize) {
+    this.initPoolSize = initPoolSize;
+  }
+
+  public String getJdbcDriver() {
+    return jdbcDriver;
+  }
+
+  public void setJdbcDriver(String jdbcDriver) {
+    this.jdbcDriver = jdbcDriver;
+  }
+
+  public String getJndiName() {
+    return jndiName;
+  }
+
+  public void setJndiName(String jndiName) {
+    this.jndiName = jndiName;
+  }
+
+  public Integer getLoginTimeout() {
+    return loginTimeout;
+  }
+
+  public void setLoginTimeout(Integer loginTimeout) {
+    this.loginTimeout = loginTimeout;
+  }
+
+  public String getManagedConnFactory() {
+    return managedConnFactory;
+  }
+
+  public void setManagedConnFactory(String managedConnFactory) {
+    this.managedConnFactory = managedConnFactory;
+  }
+
+  public Integer getMaxPoolSize() {
+    return maxPoolSize;
+  }
+
+  public void setMaxPoolSize(Integer maxPoolSize) {
+    this.maxPoolSize = maxPoolSize;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  public String getTransactionType() {
+    return transactionType;
+  }
+
+  public void setTransactionType(String transactionType) {
+    this.transactionType = transactionType;
+  }
+
+  public DATASOURCE_TYPE getType() {
+    return type;
+  }
+
+  public void setType(DATASOURCE_TYPE type) {
+    this.type = type;
+  }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  public String getXaDatasource() {
+    return xaDatasource;
+  }
+
+  public void setXaDatasource(String xaDatasource) {
+    this.xaDatasource = xaDatasource;
+  }
+
+  public List<ConfigProperty> getDatasourceConfigurations() {
+    return datasourceConfigurations;
+  }
+
+  public void setDatasourceConfigurations(List<ConfigProperty> 
dsConfigurations) {
+    this.datasourceConfigurations = dsConfigurations;
+  }
+
+  public Map getParamsAsMap() {
+    Map params = new HashMap();
+    params.put("blocking-timeout-seconds", getBlockingTimeout());
+    params.put("conn-pooled-datasource-class", getConnectionPoolDatasource());
+    params.put("connection-url", getConnectionUrl());
+    params.put("idle-timeout-seconds", getIdleTimeout());
+    params.put("init-pool-size", getInitPoolSize());
+    params.put("jdbc-driver-class", getJdbcDriver());
+    params.put("jndi-name", getJndiName());
+    params.put("login-timeout-seconds", getLoginTimeout());
+    params.put("managed-conn-factory-class", getManagedConnFactory());
+    params.put("max-pool-size", getMaxPoolSize());
+    params.put("password", getPassword());
+    params.put("transaction-type", getTransactionType());
+    params.put("type", getType().getType());
+    params.put("user-name", getUsername());
+    params.put("xa-datasource-class", getXaDatasource());
+    return params;
+  }
+}
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/Configuration.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/Configuration.java
index f0ff79c..132b8a3 100644
--- 
a/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/Configuration.java
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/Configuration.java
@@ -18,8 +18,9 @@ import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.HashSet;
 import java.util.Properties;
 import java.util.Set;
@@ -34,7 +35,7 @@ import org.xml.sax.SAXException;
 
 import org.apache.geode.DataSerializable;
 import org.apache.geode.DataSerializer;
-import org.apache.geode.internal.util.CollectionUtils;
+import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator;
 import org.apache.geode.management.internal.configuration.utils.XmlUtils;
 
 /**
@@ -72,6 +73,14 @@ public class Configuration implements DataSerializable {
     this.propertiesFileName = configName + ".properties";
     this.gemfireProperties = new Properties();
     this.jarNames = new HashSet<String>();
+    this.cacheXmlContent = generateInitialXmlContent();
+  }
+
+  private String generateInitialXmlContent() {
+    StringWriter sw = new StringWriter();
+    PrintWriter pw = new PrintWriter(sw);
+    CacheXmlGenerator.generateDefault(pw);
+    return sw.toString();
   }
 
   public String getCacheXmlContent() {
diff --git 
a/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
 
b/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
index 0b4bbc3..8e148e1 100644
--- 
a/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
+++ 
b/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
@@ -516,6 +516,7 @@ 
org/apache/geode/management/internal/cli/functions/CreateAsyncEventQueueFunction
 
org/apache/geode/management/internal/cli/functions/CreateDefinedIndexesFunction,true,1
 
org/apache/geode/management/internal/cli/functions/CreateDiskStoreFunction,true,1
 org/apache/geode/management/internal/cli/functions/CreateIndexFunction,true,1
+org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction,false
 
org/apache/geode/management/internal/cli/functions/DataCommandFunction,true,1,optimizeForWrite:boolean
 org/apache/geode/management/internal/cli/functions/DeployFunction,true,1
 
org/apache/geode/management/internal/cli/functions/DescribeDiskStoreFunction,false
@@ -544,6 +545,8 @@ 
org/apache/geode/management/internal/cli/functions/GetRegionsFunction,true,1
 
org/apache/geode/management/internal/cli/functions/GetStackTracesFunction,true,1
 
org/apache/geode/management/internal/cli/functions/GetSubscriptionQueueSizeFunction,true,1
 org/apache/geode/management/internal/cli/functions/ImportDataFunction,true,1
+org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration,false,blockingTimeout:java/lang/Integer,connectionPoolDatasource:java/lang/String,connectionUrl:java/lang/String,datasourceConfigurations:java/util/List,idleTimeout:java/lang/Integer,initPoolSize:java/lang/Integer,jdbcDriver:java/lang/String,jndiName:java/lang/String,loginTimeout:java/lang/Integer,managedConnFactory:java/lang/String,maxPoolSize:java/lang/Integer,password:java/lang/String,transactionType:java/lang
 [...]
+org/apache/geode/management/internal/cli/functions/JndiBindingConfiguration$DATASOURCE_TYPE,false,type:java/lang/String
 
org/apache/geode/management/internal/cli/functions/ListAsyncEventQueuesFunction,true,1
 org/apache/geode/management/internal/cli/functions/ListDeployedFunction,true,1
 org/apache/geode/management/internal/cli/functions/ListDiskStoresFunction,false
diff --git 
a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateAsyncEventQueueCommandDUnitTest.java
 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateAsyncEventQueueCommandDUnitTest.java
index 1bf0ff9..0075e90 100644
--- 
a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateAsyncEventQueueCommandDUnitTest.java
+++ 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateAsyncEventQueueCommandDUnitTest.java
@@ -112,7 +112,8 @@ public class CreateAsyncEventQueueCommandDUnitTest {
     locator.invoke(() -> {
       ClusterConfigurationService service =
           ClusterStartupRule.getLocator().getSharedConfiguration();
-      
assertThat(service.getConfiguration("cluster").getCacheXmlContent()).isNull();
+      assertThat(service.getConfiguration("cluster").getCacheXmlContent())
+          .doesNotContain("async-event-queue");
     });
 
     gfsh.executeAndAssertThat(VALID_COMMAND + " --id=queue").statusIsSuccess()
diff --git 
a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateDefinedIndexesCommandDUnitTest.java
 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateDefinedIndexesCommandDUnitTest.java
index c43142a..862e745 100644
--- 
a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateDefinedIndexesCommandDUnitTest.java
+++ 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateDefinedIndexesCommandDUnitTest.java
@@ -205,7 +205,10 @@ public class CreateDefinedIndexesCommandDUnitTest {
           ((InternalLocator) Locator.getLocator()).getSharedConfiguration();
       
assertThat(sharedConfig.getConfiguration("group1").getCacheXmlContent()).contains(index2Name,
           index1Name);
-      
assertThat(sharedConfig.getConfiguration("cluster").getCacheXmlContent()).isNullOrEmpty();
+      assertThat(sharedConfig.getConfiguration("cluster").getCacheXmlContent())
+          .doesNotContain(index2Name);
+      assertThat(sharedConfig.getConfiguration("cluster").getCacheXmlContent())
+          .doesNotContain(index1Name);
     });
   }
 }
diff --git 
a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandDUnitTest.java
 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandDUnitTest.java
new file mode 100644
index 0000000..76d293e
--- /dev/null
+++ 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandDUnitTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.geode.management.internal.cli.commands;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+import java.util.stream.Stream;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import org.apache.geode.distributed.internal.ClusterConfigurationService;
+import org.apache.geode.internal.jndi.JNDIInvoker;
+import org.apache.geode.management.internal.configuration.domain.Configuration;
+import org.apache.geode.management.internal.configuration.utils.XmlUtils;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.DistributedTest;
+import org.apache.geode.test.junit.rules.GfshCommandRule;
+import org.apache.geode.test.junit.rules.VMProvider;
+
+@Category(DistributedTest.class)
+public class CreateJndiBindingCommandDUnitTest {
+
+  private static MemberVM locator, server1, server2;
+
+  @ClassRule
+  public static ClusterStartupRule cluster = new ClusterStartupRule();
+
+  @ClassRule
+  public static GfshCommandRule gfsh = new GfshCommandRule();
+
+
+  @BeforeClass
+  public static void before() throws Exception {
+    locator = cluster.startLocatorVM(0);
+    server1 = cluster.startServerVM(1, "group1", locator.getPort());
+    server2 = cluster.startServerVM(2, "group1", locator.getPort());
+
+    gfsh.connectAndVerify(locator);
+  }
+
+  @Test
+  public void testCreateJndiBinding() throws Exception {
+    // assert that is no datasource
+    VMProvider.invokeInEveryMember(
+        () -> 
assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(0));
+
+    // create the binding
+    gfsh.executeAndAssertThat(
+        "create jndi-binding --name=jndi1 --type=SIMPLE 
--jdbc-driver-class=org.apache.derby.jdbc.EmbeddedDriver 
--connection-url=\"jdbc:derby:newDB;create=true\"")
+        .statusIsSuccess().tableHasColumnOnlyWithValues("Member", "server-1", 
"server-2");
+
+    // verify cluster config is updated
+    locator.invoke(() -> {
+      ClusterConfigurationService ccService =
+          ClusterStartupRule.getLocator().getSharedConfiguration();
+      Configuration configuration = ccService.getConfiguration("cluster");
+      Document document = 
XmlUtils.createDocumentFromXml(configuration.getCacheXmlContent());
+      NodeList jndiBindings = document.getElementsByTagName("jndi-binding");
+
+      assertThat(jndiBindings.getLength()).isGreaterThan(0);
+
+      boolean found = false;
+      for (int i = 0; i < jndiBindings.getLength(); i++) {
+        Element eachBinding = (Element) jndiBindings.item(i);
+        if (eachBinding.getAttribute("jndi-name").equals("jndi1")) {
+          found = true;
+          break;
+        }
+      }
+      assertThat(found).isTrue();
+    });
+
+    // verify datasource exists
+    VMProvider.invokeInEveryMember(
+        () -> 
assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(1));
+
+    // bounce server1
+    server1.stopVM(false);
+    server1 = cluster.startServerVM(1, locator.getPort());
+
+    // verify it has recreated the datasource from cluster config
+    server1.invoke(() -> {
+      assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(1);
+    });
+  }
+}
diff --git 
a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandTest.java
 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandTest.java
new file mode 100644
index 0000000..53062c6
--- /dev/null
+++ 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/CreateJndiBindingCommandTest.java
@@ -0,0 +1,408 @@
+/*
+ * 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.geode.management.internal.cli.commands;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.mockito.ArgumentCaptor;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.distributed.internal.ClusterConfigurationService;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.datasource.ConfigProperty;
+import org.apache.geode.management.internal.cli.GfshParseResult;
+import org.apache.geode.management.internal.cli.functions.CliFunctionResult;
+import 
org.apache.geode.management.internal.cli.functions.CreateJndiBindingFunction;
+import 
org.apache.geode.management.internal.cli.functions.JndiBindingConfiguration;
+import org.apache.geode.management.internal.configuration.domain.Configuration;
+import org.apache.geode.management.internal.configuration.utils.XmlUtils;
+import org.apache.geode.test.junit.categories.UnitTest;
+import org.apache.geode.test.junit.rules.GfshParserRule;
+
+@Category(UnitTest.class)
+public class CreateJndiBindingCommandTest {
+
+  @ClassRule
+  public static GfshParserRule gfsh = new GfshParserRule();
+
+  private CreateJndiBindingCommand command;
+  private InternalCache cache;
+
+  private static String COMMAND = "create jndi-binding ";
+
+  @Before
+  public void setUp() throws Exception {
+    cache = mock(InternalCache.class);
+    command = spy(CreateJndiBindingCommand.class);
+    doReturn(cache).when(command).getCache();
+  }
+
+  @Test
+  public void missingMandatory() {
+    gfsh.executeAndAssertThat(command, 
COMMAND).statusIsError().containsOutput("Invalid command");
+  }
+
+  @Test
+  public void wrongType() {
+    gfsh.executeAndAssertThat(command,
+        COMMAND
+            + " --type=NOTSOSIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url ")
+        .statusIsError().containsOutput("Invalid command");
+  }
+
+  @Test
+  public void configPropertyIsProperlyParsed() {
+    GfshParseResult result = gfsh.parse(COMMAND
+        + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url "
+        + 
"--datasource-config-properties={'name':'name1','type':'type1','value':'value1'},{'name':'name2','type':'type2','value':'value2'}");
+
+    ConfigProperty[] configProperties =
+        (ConfigProperty[]) 
result.getParamValue("datasource-config-properties");
+    assertThat(configProperties).hasSize(2);
+    assertThat(configProperties[0].getValue()).isEqualTo("value1");
+    assertThat(configProperties[1].getValue()).isEqualTo("value2");
+  }
+
+  @Test
+  public void returnsErrorIfBindingAlreadyExistsAndIfUnspecified()
+      throws ParserConfigurationException, SAXException, IOException {
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+    doReturn(true).when(command).isBindingAlreadyExists(any());
+
+    gfsh.executeAndAssertThat(command,
+        COMMAND + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url")
+        .statusIsError().containsOutput("already exists.");
+  }
+
+  @Test
+  public void skipsIfBindingAlreadyExistsAndIfSpecified()
+      throws ParserConfigurationException, SAXException, IOException {
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+    doReturn(true).when(command).isBindingAlreadyExists(any());
+
+    gfsh.executeAndAssertThat(command,
+        COMMAND
+            + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url --if-not-exists")
+        .statusIsSuccess().containsOutput("Skipping");
+  }
+
+  @Test
+  public void skipsIfBindingAlreadyExistsAndIfSpecifiedTrue()
+      throws ParserConfigurationException, SAXException, IOException {
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+    doReturn(true).when(command).isBindingAlreadyExists(any());
+
+    gfsh.executeAndAssertThat(command,
+        COMMAND
+            + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url --if-not-exists=true")
+        .statusIsSuccess().containsOutput("Skipping");
+  }
+
+  @Test
+  public void returnsErrorIfBindingAlreadyExistsAndIfSpecifiedFalse()
+      throws ParserConfigurationException, SAXException, IOException {
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+    doReturn(true).when(command).isBindingAlreadyExists(any());
+
+    gfsh.executeAndAssertThat(command,
+        COMMAND
+            + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url --if-not-exists=false")
+        .statusIsError().containsOutput("already exists.");
+  }
+
+  @Test
+  public void 
ifBindingAlreadyExistsShouldReturnTrueIfABindingExistsWithJndiName()
+      throws ParserConfigurationException, SAXException, IOException, 
TransformerException {
+
+    Configuration clusterConfig = new Configuration("cluster");
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+    Region configRegion = mock(Region.class);
+
+    doReturn(configRegion).when(clusterConfigService).getConfigurationRegion();
+    doReturn(null).when(configRegion).put(any(), any());
+    
doReturn(clusterConfig).when(clusterConfigService).getConfiguration("cluster");
+    doReturn(Collections.emptySet()).when(command).findMembers(any(), any());
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+
+    // update cluster config
+    JndiBindingConfiguration jndi = new JndiBindingConfiguration();
+    jndi.setJndiName("jndi1");
+    jndi.setType(JndiBindingConfiguration.DATASOURCE_TYPE.SIMPLE);
+    command.updateXml(jndi);
+
+    assertThat(command.isBindingAlreadyExists("jndi1")).isTrue();
+  }
+
+  @Test
+  public void ifBindingAlreadyExistsShouldReturnFalseIfNoBindingsTagExists()
+      throws ParserConfigurationException, SAXException, IOException {
+
+    Configuration clusterConfig = new Configuration("cluster");
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+
+    
doReturn(clusterConfig).when(clusterConfigService).getConfiguration("cluster");
+    doReturn(Collections.emptySet()).when(command).findMembers(any(), any());
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+
+    assertThat(command.isBindingAlreadyExists("somename")).isFalse();
+  }
+
+  @Test
+  public void ifBindingAlreadyExistsShouldReturnFalseIfNoBindingExists()
+      throws ParserConfigurationException, SAXException, IOException, 
TransformerException {
+
+    Configuration clusterConfig = new Configuration("cluster");
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+    Region configRegion = mock(Region.class);
+
+    doReturn(configRegion).when(clusterConfigService).getConfigurationRegion();
+    doReturn(null).when(configRegion).put(any(), any());
+    
doReturn(clusterConfig).when(clusterConfigService).getConfiguration("cluster");
+    doReturn(Collections.emptySet()).when(command).findMembers(any(), any());
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+
+    // update cluster config
+    JndiBindingConfiguration jndi = new JndiBindingConfiguration();
+    jndi.setJndiName("jndi1");
+    jndi.setType(JndiBindingConfiguration.DATASOURCE_TYPE.SIMPLE);
+    command.updateXml(jndi);
+
+    assertThat(command.isBindingAlreadyExists("jndi2")).isFalse();
+  }
+
+  @Test
+  public void updateXmlShouldClusterConfigurationWithJndiConfiguration()
+      throws IOException, ParserConfigurationException, SAXException, 
TransformerException {
+    Configuration clusterConfig = new Configuration("cluster");
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+    Region configRegion = mock(Region.class);
+
+    doReturn(configRegion).when(clusterConfigService).getConfigurationRegion();
+    doReturn(null).when(configRegion).put(any(), any());
+    
doReturn(clusterConfig).when(clusterConfigService).getConfiguration("cluster");
+    doReturn(Collections.emptySet()).when(command).findMembers(any(), any());
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+
+    JndiBindingConfiguration jndi = new JndiBindingConfiguration();
+    jndi.setBlockingTimeout(60);
+    jndi.setConnectionUrl("URL");
+    jndi.setConnectionPoolDatasource("org.datasource");
+    jndi.setMaxPoolSize(10);
+    jndi.setManagedConnFactory("connFactory");
+    jndi.setLoginTimeout(100);
+    jndi.setJndiName("jndi1");
+    jndi.setJdbcDriver("driver");
+    jndi.setUsername("user1");
+    jndi.setIdleTimeout(50);
+    jndi.setInitPoolSize(5);
+    jndi.setPassword("p@ssw0rd");
+    jndi.setTransactionType("txntype");
+    jndi.setType(JndiBindingConfiguration.DATASOURCE_TYPE.SIMPLE);
+    jndi.setXaDatasource("xaDS");
+    ConfigProperty prop = new ConfigProperty("somename", "somevalue", 
"sometype");
+    jndi.setDatasourceConfigurations(Arrays.asList(new ConfigProperty[] 
{prop}));
+    command.updateXml(jndi);
+
+    Document document = 
XmlUtils.createDocumentFromXml(clusterConfig.getCacheXmlContent());
+    assertThat(document).isNotNull();
+    
assertThat(document.getElementsByTagName("jndi-bindings").item(0)).isNotNull();
+    Element jndiElement = (Element) 
document.getElementsByTagName("jndi-binding").item(0);
+    assertThat(jndiElement).isNotNull();
+    
assertThat(jndiElement.getParentNode().getNodeName()).isEqualTo("jndi-bindings");
+    
assertThat(jndiElement.getAttribute("blocking-timeout-seconds")).isEqualTo("60");
+    assertThat(jndiElement.getAttribute("conn-pooled-datasource-class"))
+        .isEqualTo("org.datasource");
+    assertThat(jndiElement.getAttribute("connection-url")).isEqualTo("URL");
+    
assertThat(jndiElement.getAttribute("idle-timeout-seconds")).isEqualTo("50");
+    assertThat(jndiElement.getAttribute("init-pool-size")).isEqualTo("5");
+    
assertThat(jndiElement.getAttribute("jdbc-driver-class")).isEqualTo("driver");
+    assertThat(jndiElement.getAttribute("jndi-name")).isEqualTo("jndi1");
+    
assertThat(jndiElement.getAttribute("login-timeout-seconds")).isEqualTo("100");
+    
assertThat(jndiElement.getAttribute("managed-conn-factory-class")).isEqualTo("connFactory");
+    assertThat(jndiElement.getAttribute("max-pool-size")).isEqualTo("10");
+    assertThat(jndiElement.getAttribute("password")).isEqualTo("p@ssw0rd");
+    
assertThat(jndiElement.getAttribute("transaction-type")).isEqualTo("txntype");
+    assertThat(jndiElement.getAttribute("type")).isEqualTo("SimpleDataSource");
+    assertThat(jndiElement.getAttribute("user-name")).isEqualTo("user1");
+    
assertThat(jndiElement.getAttribute("xa-datasource-class")).isEqualTo("xaDS");
+
+    Node configProperty = 
document.getElementsByTagName("config-property").item(0);
+    
assertThat(configProperty.getParentNode().getNodeName()).isEqualTo("jndi-binding");
+
+    Node nameProperty = 
document.getElementsByTagName("config-property-name").item(0);
+    assertThat(nameProperty).isNotNull();
+    
assertThat(nameProperty.getParentNode().getNodeName()).isEqualTo("config-property");
+    assertThat(nameProperty.getTextContent()).isEqualTo("somename");
+    Node typeProperty = 
document.getElementsByTagName("config-property-type").item(0);
+    assertThat(typeProperty).isNotNull();
+    
assertThat(nameProperty.getParentNode().getNodeName()).isEqualTo("config-property");
+    assertThat(typeProperty.getTextContent()).isEqualTo("sometype");
+    Node valueProperty = 
document.getElementsByTagName("config-property-value").item(0);
+    assertThat(valueProperty).isNotNull();
+    
assertThat(nameProperty.getParentNode().getNodeName()).isEqualTo("config-property");
+    assertThat(valueProperty.getTextContent()).isEqualTo("somevalue");
+
+    verify(configRegion, times(1)).put("cluster", clusterConfig);
+  }
+
+  @Test
+  public void whenNoMembersFoundAndNoClusterConfigServiceRunningThenError()
+      throws IOException, ParserConfigurationException, SAXException, 
TransformerException {
+
+    doReturn(Collections.emptySet()).when(command).findMembers(any(), any());
+    doReturn(false).when(command).isBindingAlreadyExists(any());
+    doNothing().when(command).updateXml(any());
+
+    gfsh.executeAndAssertThat(command,
+        COMMAND + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url")
+        // .statusIsSuccess().containsOutput("Cluster Configuration Service is 
not available")
+        .statusIsSuccess().containsOutput("No members 
found").hasFailToPersistError();
+  }
+
+  @Test
+  public void 
whenNoMembersFoundAndClusterConfigRunningThenUpdateClusterConfig()
+      throws IOException, ParserConfigurationException, SAXException, 
TransformerException {
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+
+    doReturn(Collections.emptySet()).when(command).findMembers(any(), any());
+    doReturn(false).when(command).isBindingAlreadyExists(any());
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+    doNothing().when(command).updateXml(any());
+
+    gfsh.executeAndAssertThat(command,
+        COMMAND + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url")
+        .statusIsSuccess();
+  }
+
+  @Test
+  public void whenMembersFoundAndNoClusterConfigRunningThenOnlyInvokeFunction()
+      throws IOException, ParserConfigurationException, SAXException, 
TransformerException {
+    Set<DistributedMember> members = new HashSet<>();
+    members.add(mock(DistributedMember.class));
+
+    CliFunctionResult result = new CliFunctionResult("server1", true,
+        "Tried creating jndi binding \"name\" on \"server1\"");
+    List<CliFunctionResult> results = new ArrayList<>();
+    results.add(result);
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+
+    doReturn(members).when(command).findMembers(any(), any());
+    doReturn(false).when(command).isBindingAlreadyExists(any());
+    doReturn(null).when(command).getSharedConfiguration();
+    doReturn(results).when(command).executeAndGetFunctionResult(any(), any(), 
any());
+
+    gfsh.executeAndAssertThat(command,
+        COMMAND
+            + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url 
--datasource-config-properties={'name':'name1','type':'type1','value':'value1'}")
+        .statusIsSuccess().tableHasColumnOnlyWithValues("Member", "server1")
+        .tableHasColumnOnlyWithValues("Status",
+            "Tried creating jndi binding \"name\" on \"server1\"");
+
+    verify(command, times(0)).updateXml(any());
+
+    ArgumentCaptor<CreateJndiBindingFunction> function =
+        ArgumentCaptor.forClass(CreateJndiBindingFunction.class);
+    ArgumentCaptor<JndiBindingConfiguration> jndiConfig =
+        ArgumentCaptor.forClass(JndiBindingConfiguration.class);
+    ArgumentCaptor<Set<DistributedMember>> targetMembers = 
ArgumentCaptor.forClass(Set.class);
+    verify(command, times(1)).executeAndGetFunctionResult(function.capture(), 
jndiConfig.capture(),
+        targetMembers.capture());
+
+    
assertThat(function.getValue()).isInstanceOf(CreateJndiBindingFunction.class);
+    assertThat(jndiConfig.getValue()).isNotNull();
+    assertThat(jndiConfig.getValue().getJndiName()).isEqualTo("name");
+    
assertThat(jndiConfig.getValue().getDatasourceConfigurations().get(0).getName())
+        .isEqualTo("name1");
+    assertThat(targetMembers.getValue()).isEqualTo(members);
+  }
+
+  @Test
+  public void 
whenMembersFoundAndClusterConfigRunningThenUpdateClusterConfigAndInvokeFunction()
+      throws IOException, ParserConfigurationException, SAXException, 
TransformerException {
+    Set<DistributedMember> members = new HashSet<>();
+    members.add(mock(DistributedMember.class));
+
+    CliFunctionResult result = new CliFunctionResult("server1", true,
+        "Tried creating jndi binding \"name\" on \"server1\"");
+    List<CliFunctionResult> results = new ArrayList<>();
+    results.add(result);
+    ClusterConfigurationService clusterConfigService = 
mock(ClusterConfigurationService.class);
+
+    doReturn(members).when(command).findMembers(any(), any());
+    doReturn(false).when(command).isBindingAlreadyExists(any());
+    doReturn(clusterConfigService).when(command).getSharedConfiguration();
+    doNothing().when(command).updateXml(any());
+    doReturn(results).when(command).executeAndGetFunctionResult(any(), any(), 
any());
+
+    gfsh.executeAndAssertThat(command,
+        COMMAND
+            + " --type=SIMPLE --name=name --jdbc-driver-class=driver 
--connection-url=url 
--datasource-config-properties={'name':'name1','type':'type1','value':'value1'}")
+        .statusIsSuccess().tableHasColumnOnlyWithValues("Member", "server1")
+        .tableHasColumnOnlyWithValues("Status",
+            "Tried creating jndi binding \"name\" on \"server1\"");
+
+    verify(command, times(1)).updateXml(any());
+
+    ArgumentCaptor<CreateJndiBindingFunction> function =
+        ArgumentCaptor.forClass(CreateJndiBindingFunction.class);
+    ArgumentCaptor<JndiBindingConfiguration> jndiConfig =
+        ArgumentCaptor.forClass(JndiBindingConfiguration.class);
+    ArgumentCaptor<Set<DistributedMember>> targetMembers = 
ArgumentCaptor.forClass(Set.class);
+    verify(command, times(1)).executeAndGetFunctionResult(function.capture(), 
jndiConfig.capture(),
+        targetMembers.capture());
+
+    
assertThat(function.getValue()).isInstanceOf(CreateJndiBindingFunction.class);
+    assertThat(jndiConfig.getValue()).isNotNull();
+    assertThat(jndiConfig.getValue().getJndiName()).isEqualTo("name");
+    
assertThat(jndiConfig.getValue().getDatasourceConfigurations().get(0).getName())
+        .isEqualTo("name1");
+    assertThat(targetMembers.getValue()).isEqualTo(members);
+  }
+}
diff --git 
a/geode-core/src/test/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverterTest.java
 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverterTest.java
new file mode 100644
index 0000000..a32d3a6
--- /dev/null
+++ 
b/geode-core/src/test/java/org/apache/geode/management/internal/cli/converters/ConfigPropertyConverterTest.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.geode.management.internal.cli.converters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.internal.datasource.ConfigProperty;
+import org.apache.geode.test.junit.categories.UnitTest;
+
+
+@Category(UnitTest.class)
+public class ConfigPropertyConverterTest {
+
+  private ConfigPropertyConverter converter;
+
+  @Before
+  public void setUp() throws Exception {
+    converter = new ConfigPropertyConverter();
+  }
+
+  @Test
+  public void validJson() {
+    ConfigProperty configProperty =
+        
converter.convertFromText("{'name':'name','type':'type','value':'value'}", 
null, null);
+    assertThat(configProperty.getName()).isEqualTo("name");
+    assertThat(configProperty.getType()).isEqualTo("type");
+    assertThat(configProperty.getValue()).isEqualTo("value");
+  }
+
+  @Test
+  public void invalidJson() {
+    assertThatThrownBy(() -> converter.convertFromText(
+        "{'name':'name','type':'type','value':'value','another':'another'}", 
null, null))
+            .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void invalidWhenEmptyString() {
+    assertThatThrownBy(() -> converter.convertFromText("", null, null))
+        .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void validWhenTypeMissing() {
+    ConfigProperty configProperty =
+        converter.convertFromText("{'name':'name','value':'value'}", null, 
null);
+    assertThat(configProperty.getName()).isEqualTo("name");
+    assertThat(configProperty.getType()).isNull();
+    assertThat(configProperty.getValue()).isEqualTo("value");
+  }
+
+  @Test
+  public void inValidWhenTypo() {
+    assertThatThrownBy(() -> converter
+        .convertFromText("{'name':'name','typo':'type','value':'value'}", 
null, null))
+            .isInstanceOf(IllegalArgumentException.class);;
+  }
+}

-- 
To stop receiving notification emails like this one, please contact
sai_boorlaga...@apache.org.

Reply via email to