You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by nr...@apache.org on 2017/11/22 18:30:31 UTC

[geode] 01/01: GEODE-3824: Allow JDBC connection configuration for gfsh

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

nreich pushed a commit to branch feature/GEODE-3824
in repository https://gitbox.apache.org/repos/asf/geode.git

commit a54832f3e1a05ee74cc42bd1385ae05a9081482a
Author: Kirk Lund <kl...@apache.org>
AuthorDate: Mon Nov 20 09:15:42 2017 -0800

    GEODE-3824: Allow JDBC connection configuration for gfsh
    
      * Fix XML parsing issues that prevented new command from functioning
      * Add missing support for extra database connection parameters
---
 .../{xml => }/ConnectionConfigBuilder.java         |  33 ++++--
 .../jdbc/internal/ConnectionConfiguration.java     |  42 +++++++-
 .../jdbc/internal/ConnectionManager.java           |   2 +-
 .../jdbc/internal/JdbcConnectorService.java        |   2 +
 .../jdbc/internal/cli/CreateConnectionCommand.java | 114 +++++++++++++++++++++
 .../internal/cli/CreateConnectionFunction.java     |  90 ++++++++++++++++
 .../connectors/jdbc/internal/xml/ElementType.java  |  13 ++-
 .../xml/JdbcConnectorServiceXmlGenerator.java      |  23 ++++-
 .../xml/JdbcConnectorServiceXmlParser.java         |   4 +-
 .../jdbc/internal/xml/RegionMappingBuilder.java    |   6 ++
 .../geode.apache.org/schema/jdbc/jdbc-1.0.xsd      |  85 +++++++++++++++
 .../jdbc/internal/ConnectionConfigurationTest.java |  22 +++-
 .../jdbc/internal/ConnectionManagerUnitTest.java   |   2 +-
 .../jdbc/internal/TestConfigService.java           |   2 +-
 .../cli/CreateConnectionCommandDUnitTest.java      |  91 ++++++++++++++++
 .../CreateConnectionCommandIntegrationTest.java    |  79 ++++++++++++++
 .../internal/cli/CreateConnectionFunctionTest.java |  87 ++++++++++++++++
 .../internal/xml/ConnectionConfigBuilderTest.java  |   4 +-
 .../jdbc/internal/xml/ElementTypeTest.java         |   1 +
 ...onnectorServiceXmlGeneratorIntegrationTest.java | 114 +++++++++++++++++++--
 .../JdbcConnectorServiceXmlIntegrationTest.java    |   3 +-
 .../internal/configuration/domain/XmlEntity.java   |  41 +++++++-
 22 files changed, 822 insertions(+), 38 deletions(-)

diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/ConnectionConfigBuilder.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfigBuilder.java
similarity index 56%
rename from geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/ConnectionConfigBuilder.java
rename to geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfigBuilder.java
index 34d61ad..b510fab 100644
--- a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/ConnectionConfigBuilder.java
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfigBuilder.java
@@ -12,37 +12,52 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.connectors.jdbc.internal.xml;
+package org.apache.geode.connectors.jdbc.internal;
 
-import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
+import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.PARAMS_DELIMITER;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ConnectionConfigBuilder {
 
-class ConnectionConfigBuilder {
   private String name;
   private String url;
   private String user;
   private String password;
+  private Map<String, String> parameters = new HashMap<>();
 
-  ConnectionConfigBuilder withName(String name) {
+  public ConnectionConfigBuilder withName(String name) {
     this.name = name;
     return this;
   }
 
-  ConnectionConfigBuilder withUrl(String url) {
+  public ConnectionConfigBuilder withUrl(String url) {
     this.url = url;
     return this;
   }
 
-  ConnectionConfigBuilder withUser(String user) {
+  public ConnectionConfigBuilder withUser(String user) {
     this.user = user;
     return this;
   }
 
-  ConnectionConfigBuilder withPassword(String password) {
+  public ConnectionConfigBuilder withPassword(String password) {
     this.password = password;
     return this;
   }
 
-  ConnectionConfiguration build() {
-    return new ConnectionConfiguration(name, url, user, password);
+  public ConnectionConfigBuilder withParameters(String[] params) {
+    for (String param : params) {
+      String[] keyValuePair = param.split(PARAMS_DELIMITER);
+      if (keyValuePair.length == 2) {
+       parameters.put(keyValuePair[0], keyValuePair[1]);
+      }
+    }
+    return this;
+  }
+
+  public ConnectionConfiguration build() {
+    return new ConnectionConfiguration(name, url, user, password, parameters);
   }
 }
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfiguration.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfiguration.java
index fa3807d..bd2a597 100644
--- a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfiguration.java
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfiguration.java
@@ -14,18 +14,28 @@
  */
 package org.apache.geode.connectors.jdbc.internal;
 
-public class ConnectionConfiguration {
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class ConnectionConfiguration implements Serializable {
+  private static final Object USER = "user";
+  private static final Object PASSWORD = "password";
 
   private final String name;
   private final String url;
   private final String user;
   private final String password;
+  private final Map<String, String> parameters;
 
-  public ConnectionConfiguration(String name, String url, String user, String password) {
+  public ConnectionConfiguration(String name, String url, String user, String password, Map<String, String> parameters) {
     this.name = name;
     this.url = url;
     this.user = user;
     this.password = password;
+    this.parameters = parameters == null ? new HashMap<>() : Collections.unmodifiableMap(parameters);
   }
 
   public String getName() {
@@ -44,6 +54,19 @@ public class ConnectionConfiguration {
     return password;
   }
 
+  public Properties getConnectionProperties() {
+    Properties properties = new Properties();
+
+    properties.putAll(parameters);
+    if (user != null) {
+      properties.put(USER, user);
+    }
+    if (password != null) {
+      properties.put(PASSWORD, password);
+    }
+    return properties;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -64,7 +87,10 @@ public class ConnectionConfiguration {
     if (user != null ? !user.equals(that.user) : that.user != null) {
       return false;
     }
-    return password != null ? password.equals(that.password) : that.password == null;
+    if (password != null ? !password.equals(that.password) : that.password != null) {
+      return false;
+    }
+    return parameters != null ? parameters.equals(that.parameters) : that.parameters == null;
   }
 
   @Override
@@ -73,12 +99,18 @@ public class ConnectionConfiguration {
     result = 31 * result + (url != null ? url.hashCode() : 0);
     result = 31 * result + (user != null ? user.hashCode() : 0);
     result = 31 * result + (password != null ? password.hashCode() : 0);
+    result = 31 * result + (parameters != null ? parameters.hashCode() : 0);
     return result;
   }
 
   @Override
   public String toString() {
-    return "ConnectionConfiguration{" + "name='" + name + '\'' + ", url='" + url + '\'' + ", user='"
-        + user + '\'' + ", password='" + password + '\'' + '}';
+    return "ConnectionConfiguration{" +
+        "name='" + name + '\'' +
+        ", url='" + url + '\'' +
+        ", user='" + user + '\'' +
+        ", password='" + password + '\'' +
+        ", parameters=" + parameters +
+        '}';
   }
 }
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionManager.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionManager.java
index d382112..30d030f 100644
--- a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionManager.java
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/ConnectionManager.java
@@ -99,7 +99,7 @@ class ConnectionManager {
 
   // package protected for testing purposes only
   Connection getSQLConnection(ConnectionConfiguration config) throws SQLException {
-    return DriverManager.getConnection(config.getUrl(), config.getUser(), config.getPassword());
+    return DriverManager.getConnection(config.getUrl(), config.getConnectionProperties());
   }
 
   private synchronized Connection getNewConnection(ConnectionConfiguration config) {
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/JdbcConnectorService.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/JdbcConnectorService.java
index 9af7aeb..5c86140 100644
--- a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/JdbcConnectorService.java
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/JdbcConnectorService.java
@@ -32,10 +32,12 @@ public class JdbcConnectorService implements InternalJdbcConnectorService {
   private volatile InternalCache cache;
   private boolean registered;
 
+  @Override
   public ConnectionConfiguration getConnectionConfig(String connectionName) {
     return connectionsByName.get(connectionName);
   }
 
+  @Override
   public RegionMapping getMappingForRegion(String regionName) {
     return mappingsByRegion.get(regionName);
   }
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommand.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommand.java
new file mode 100644
index 0000000..f16247f
--- /dev/null
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommand.java
@@ -0,0 +1,114 @@
+/*
+ * 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.connectors.jdbc.internal.cli;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.logging.log4j.Logger;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+
+import org.apache.geode.cache.execute.ResultCollector;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfigBuilder;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
+import org.apache.geode.distributed.DistributedMember;
+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.commands.GfshCommand;
+import org.apache.geode.management.internal.cli.functions.CliFunctionResult;
+import org.apache.geode.management.internal.cli.i18n.CliStrings;
+import org.apache.geode.management.internal.cli.result.ResultBuilder;
+import org.apache.geode.management.internal.cli.result.TabularResultData;
+import org.apache.geode.management.internal.configuration.domain.XmlEntity;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class CreateConnectionCommand implements GfshCommand {
+  private static final Logger logger = LogService.getLogger();
+
+  static final String CREATE_CONNECTION = "create jdbc-connection";
+  static final String CREATE_CONNECTION__HELP = "Create JDBC connection for JDBC Connector.";
+  static final String CREATE_CONNECTION__NAME = "name";
+  static final String CREATE_CONNECTION__NAME__HELP = "Name of the JDBC connection to be created.";
+  static final String CREATE_CONNECTION__URL = "url";
+  static final String CREATE_CONNECTION__URL__HELP = "URL location for the database";
+  static final String CREATE_CONNECTION__USER = "user";
+  static final String CREATE_CONNECTION__USER__HELP =
+      "Name of user to use when connecting to the database";
+  static final String CREATE_CONNECTION__PASSWORD = "password";
+  static final String CREATE_CONNECTION__PASSWORD__HELP =
+      "Password of user to use when connecting to the database";
+  static final String CREATE_CONNECTION__PARAMS = "params";
+  static final String CREATE_CONNECTION__PARAMS__HELP =
+      "Comma delimited list of additional parameters to use when connecting to the database";
+
+  private static final String ERROR_PREFIX = "ERROR: ";
+
+  @CliCommand(value = CREATE_CONNECTION, help = CREATE_CONNECTION__HELP)
+  @CliMetaData(relatedTopic = CliStrings.TOPIC_GEODE_REGION)
+  @ResourceOperation(resource = ResourcePermission.Resource.CLUSTER,
+      operation = ResourcePermission.Operation.MANAGE)
+  public Result createConnection(
+      @CliOption(key = CREATE_CONNECTION__NAME, mandatory = true,
+          help = CREATE_CONNECTION__NAME__HELP) String name,
+      @CliOption(key = CREATE_CONNECTION__URL, mandatory = true,
+          help = CREATE_CONNECTION__URL__HELP) String url,
+      @CliOption(key = CREATE_CONNECTION__USER, help = CREATE_CONNECTION__USER__HELP) String user,
+      @CliOption(key = CREATE_CONNECTION__PASSWORD,
+          help = CREATE_CONNECTION__PASSWORD__HELP) String password,
+      @CliOption(key = CREATE_CONNECTION__PARAMS,
+          help = CREATE_CONNECTION__PARAMS__HELP) String[] params) {
+
+    Set<DistributedMember> membersToCreateConnectionOn = getMembers(null, null);
+
+    ConnectionConfigBuilder builder = new ConnectionConfigBuilder().withName(name)
+        .withUrl(url).withUser(user).withPassword(password).withParameters(params);
+    ConnectionConfiguration configuration = builder.build();
+
+    ResultCollector<?, ?> resultCollector =
+        executeFunction(new CreateConnectionFunction(), configuration, membersToCreateConnectionOn);
+
+    Object resultCollectorResult = resultCollector.getResult();
+
+    List<CliFunctionResult> regionCreateResults = (List<CliFunctionResult>) resultCollectorResult;
+
+    AtomicReference<XmlEntity> xmlEntity = new AtomicReference<>();
+    TabularResultData tabularResultData = ResultBuilder.createTabularResultData();
+    for (CliFunctionResult regionCreateResult : regionCreateResults) {
+      boolean success = regionCreateResult.isSuccessful();
+      tabularResultData.accumulate("Member", regionCreateResult.getMemberIdOrName());
+      tabularResultData.accumulate("Status",
+          (success ? "" : ERROR_PREFIX) + regionCreateResult.getMessage());
+
+      if (success) {
+        xmlEntity.set(regionCreateResult.getXmlEntity());
+      } else {
+        tabularResultData.setStatus(Result.Status.ERROR);
+      }
+    }
+
+    Result result = ResultBuilder.buildResult(tabularResultData);
+
+    if (xmlEntity.get() != null) {
+      persistClusterConfiguration(result,
+          () -> getSharedConfiguration().addXmlEntity(xmlEntity.get(), null));
+    }
+
+    return result;
+  }
+}
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionFunction.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionFunction.java
new file mode 100644
index 0000000..73cb4bb
--- /dev/null
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionFunction.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.connectors.jdbc.internal.cli;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.cache.execute.ResultSender;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
+import org.apache.geode.connectors.jdbc.internal.InternalJdbcConnectorService;
+import org.apache.geode.connectors.jdbc.internal.xml.ElementType;
+import org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlGenerator;
+import org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser;
+import org.apache.geode.internal.InternalEntity;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.cache.xmlcache.CacheXml;
+import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.management.internal.cli.CliUtil;
+import org.apache.geode.management.internal.cli.functions.CliFunctionResult;
+import org.apache.geode.management.internal.configuration.domain.XmlEntity;
+
+public class CreateConnectionFunction implements Function<ConnectionConfiguration>, InternalEntity {
+  private static final Logger logger = LogService.getLogger();
+
+  private static final String ID = CreateConnectionFunction.class.getName();
+
+  @Override
+  public boolean isHA() {
+    return false;
+  }
+
+  @Override
+  public String getId() {
+    return ID;
+  }
+
+  @Override
+  public void execute(FunctionContext<ConnectionConfiguration> context) {
+    ResultSender<Object> resultSender = context.getResultSender();
+
+    InternalCache cache = (InternalCache) context.getCache();
+    String memberNameOrId =
+        CliUtil.getMemberNameOrId(cache.getDistributedSystem().getDistributedMember());
+
+    ConnectionConfiguration configuration = context.getArguments();
+
+    try {
+      InternalJdbcConnectorService service = cache.getService(InternalJdbcConnectorService.class);
+      service.addOrUpdateConnectionConfig(configuration);
+
+      XmlEntity xmlEntity = new XmlEntity(CacheXml.CACHE, JdbcConnectorServiceXmlGenerator.PREFIX,
+          JdbcConnectorServiceXmlParser.NAMESPACE, ElementType.CONNECTION_SERVICE.getTypeName());
+
+      resultSender.lastResult(new CliFunctionResult(memberNameOrId, xmlEntity,
+          "Created JDBC connection " + configuration.getName() + " on " + memberNameOrId));
+
+    } catch (Exception e) {
+      String exceptionMsg = e.getMessage();
+      if (exceptionMsg == null) {
+        exceptionMsg = CliUtil.stackTraceAsString(e);
+      }
+      resultSender.lastResult(handleException(memberNameOrId, exceptionMsg, e));
+    }
+  }
+
+  private CliFunctionResult handleException(final String memberNameOrId, final String exceptionMsg,
+      final Exception e) {
+    if (e != null && logger.isDebugEnabled()) {
+      logger.debug(e.getMessage(), e);
+    }
+    if (exceptionMsg != null) {
+      return new CliFunctionResult(memberNameOrId, false, exceptionMsg);
+    }
+
+    return new CliFunctionResult(memberNameOrId);
+  }
+}
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/ElementType.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/ElementType.java
index 580910c..c7ef034 100644
--- a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/ElementType.java
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/ElementType.java
@@ -19,11 +19,12 @@ import java.util.Stack;
 import org.xml.sax.Attributes;
 
 import org.apache.geode.cache.CacheXmlException;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfigBuilder;
 import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
 import org.apache.geode.connectors.jdbc.internal.RegionMapping;
 import org.apache.geode.internal.cache.xmlcache.CacheCreation;
 
-enum ElementType {
+public enum ElementType {
   CONNECTION_SERVICE("connector-service") {
     @Override
     void startElement(Stack<Object> stack, Attributes attributes) {
@@ -54,9 +55,17 @@ enum ElementType {
           .withUrl(attributes.getValue(JdbcConnectorServiceXmlParser.URL))
           .withUser(attributes.getValue(JdbcConnectorServiceXmlParser.USER))
           .withPassword(attributes.getValue(JdbcConnectorServiceXmlParser.PASSWORD));
+      addParameters(connectionConfig, attributes.getValue(JdbcConnectorServiceXmlParser.PARAMETERS));
       stack.push(connectionConfig);
     }
 
+    private void addParameters(ConnectionConfigBuilder connectionConfig, String value) {
+      if (value == null) {
+        return;
+      }
+      connectionConfig.withParameters(value.split(","));
+    }
+
     @Override
     void endElement(Stack<Object> stack) {
       ConnectionConfiguration config = ((ConnectionConfigBuilder) stack.pop()).build();
@@ -120,7 +129,7 @@ enum ElementType {
     throw new IllegalArgumentException("Invalid type '" + typeName + "'");
   }
 
-  String getTypeName() {
+  public String getTypeName() {
     return typeName;
   }
 
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlGenerator.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlGenerator.java
index 867dd7f..afc929b 100644
--- a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlGenerator.java
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlGenerator.java
@@ -19,6 +19,8 @@ import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorService
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.FIELD_NAME;
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.NAME;
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.NAMESPACE;
+import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.PARAMETERS;
+import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.PARAMS_DELIMITER;
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.PASSWORD;
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.PDX_CLASS;
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.PRIMARY_KEY_IN_VALUE;
@@ -30,6 +32,7 @@ import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorService
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Properties;
 
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
@@ -43,8 +46,10 @@ import org.apache.geode.internal.cache.xmlcache.XmlGenerator;
 import org.apache.geode.internal.cache.xmlcache.XmlGeneratorUtils;
 
 public class JdbcConnectorServiceXmlGenerator implements XmlGenerator<Cache> {
+
+  public static final String PREFIX = "jdbc";
+
   private static final AttributesImpl EMPTY = new AttributesImpl();
-  static final String PREFIX = "jdbc";
 
   private final Collection<ConnectionConfiguration> connections;
   private final Collection<RegionMapping> mappings;
@@ -97,6 +102,7 @@ public class JdbcConnectorServiceXmlGenerator implements XmlGenerator<Cache> {
     XmlGeneratorUtils.addAttribute(attributes, URL, config.getUrl());
     XmlGeneratorUtils.addAttribute(attributes, USER, config.getUser());
     XmlGeneratorUtils.addAttribute(attributes, PASSWORD, config.getPassword());
+    XmlGeneratorUtils.addAttribute(attributes, PARAMETERS, createParametersString(config));
     XmlGeneratorUtils.emptyElement(handler, PREFIX, ElementType.CONNECTION.getTypeName(),
         attributes);
   }
@@ -127,4 +133,19 @@ public class JdbcConnectorServiceXmlGenerator implements XmlGenerator<Cache> {
           fieldAttributes);
     }
   }
+
+  private String createParametersString(ConnectionConfiguration config) {
+    Properties properties = config.getConnectionProperties();
+    StringBuilder stringBuilder = new StringBuilder();
+    for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+      Object key = entry.getKey();
+      if (!key.equals(USER) && !key.equals(PASSWORD)) {
+        if (stringBuilder.length() > 0) {
+          stringBuilder.append(",");
+        }
+        stringBuilder.append(key).append(PARAMS_DELIMITER).append(entry.getValue());
+      }
+    }
+    return stringBuilder.toString();
+  }
 }
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlParser.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlParser.java
index 7caaa1d..7fdd691 100644
--- a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlParser.java
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlParser.java
@@ -20,11 +20,13 @@ import org.xml.sax.SAXException;
 import org.apache.geode.internal.cache.xmlcache.AbstractXmlParser;
 
 public class JdbcConnectorServiceXmlParser extends AbstractXmlParser {
-  static final String NAMESPACE = "http://geode.apache.org/schema/jdbc-connector";
+  public static final String NAMESPACE = "http://geode.apache.org/schema/jdbc";
+  public static final String PARAMS_DELIMITER = ":";
   static final String NAME = "name";
   static final String URL = "url";
   static final String USER = "user";
   static final String PASSWORD = "password";
+  static final String PARAMETERS = "parameters";
   static final String REGION = "region";
   static final String CONNECTION_NAME = "connection-name";
   static final String TABLE = "table";
diff --git a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/RegionMappingBuilder.java b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/RegionMappingBuilder.java
index b4adcaf..48c8543 100644
--- a/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/RegionMappingBuilder.java
+++ b/geode-connectors/src/main/java/org/apache/geode/connectors/jdbc/internal/xml/RegionMappingBuilder.java
@@ -48,11 +48,17 @@ class RegionMappingBuilder {
     return this;
   }
 
+  // TODO: delete withPrimaryKeyInValue(String)
   RegionMappingBuilder withPrimaryKeyInValue(String primaryKeyInValue) {
     this.primaryKeyInValue = Boolean.parseBoolean(primaryKeyInValue);
     return this;
   }
 
+  RegionMappingBuilder withPrimaryKeyInValue(boolean primaryKeyInValue) {
+    this.primaryKeyInValue = primaryKeyInValue;
+    return this;
+  }
+
   RegionMappingBuilder withFieldToColumnMapping(String fieldName, String columnMapping) {
     this.fieldToColumnMap.put(fieldName, columnMapping);
     return this;
diff --git a/geode-connectors/src/main/resources/META-INF/services/schemas/geode.apache.org/schema/jdbc/jdbc-1.0.xsd b/geode-connectors/src/main/resources/META-INF/services/schemas/geode.apache.org/schema/jdbc/jdbc-1.0.xsd
new file mode 100644
index 0000000..1c913dd
--- /dev/null
+++ b/geode-connectors/src/main/resources/META-INF/services/schemas/geode.apache.org/schema/jdbc/jdbc-1.0.xsd
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<xsd:schema
+        targetNamespace="http://geode.apache.org/schema/jdbc"
+        xmlns:gf="http://geode.apache.org/schema/cache"
+        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+        elementFormDefault="qualified"
+        attributeFormDefault="unqualified"
+        version="1.0">
+
+    <xsd:import
+            namespace="http://geode.apache.org/schema/cache"
+            schemaLocation="http://geode.apache.org/schema/cache/cache-1.0.xsd"/>
+
+    <xsd:annotation>
+        <xsd:documentation><![CDATA[
+XML schema for JDBC Connector Service in Geode.
+
+  <cache
+    xmlns="http://geode.apache.org/schema/cache"
+    xmlns:lucene="http://geode.apache.org/schema/jdbc"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://geode.apache.org/schema/cache
+        http://geode.apache.org/schema/cache/cache-1.0.xsd
+        http://geode.apache.org/schema/jdbc
+        http://geode.apache.org/schema/jdbc/jdbc-1.0.xsd"
+    version="1.0">
+
+    ]]></xsd:documentation>
+    </xsd:annotation>
+    <xsd:element name="connector-service">
+        <xsd:complexType>
+            <xsd:sequence>
+                <xsd:element name="connection" maxOccurs="unbounded" minOccurs="0">
+                    <xsd:complexType>
+                        <xsd:simpleContent>
+                            <xsd:extension base="xsd:string">
+                                <xsd:attribute type="xsd:string" name="name" use="optional"/>
+                                <xsd:attribute type="xsd:string" name="url" use="optional"/>
+                                <xsd:attribute type="xsd:string" name="user" use="optional"/>
+                                <xsd:attribute type="xsd:string" name="password" use="optional"/>
+                            </xsd:extension>
+                        </xsd:simpleContent>
+                    </xsd:complexType>
+                </xsd:element>
+                <xsd:element name="region-mapping" maxOccurs="unbounded" minOccurs="0">
+                    <xsd:complexType>
+                        <xsd:sequence>
+                            <xsd:element name="field-mapping" maxOccurs="unbounded" minOccurs="0">
+                                <xsd:complexType>
+                                    <xsd:simpleContent>
+                                        <xsd:extension base="xsd:string">
+                                            <xsd:attribute type="xsd:string" name="field-name" use="optional"/>
+                                            <xsd:attribute type="xsd:string" name="column-name" use="optional"/>
+                                        </xsd:extension>
+                                    </xsd:simpleContent>
+                                </xsd:complexType>
+                            </xsd:element>
+                        </xsd:sequence>
+                        <xsd:attribute type="xsd:string" name="connection-name" use="optional"/>
+                        <xsd:attribute type="xsd:string" name="region" use="optional"/>
+                        <xsd:attribute type="xsd:string" name="table" use="optional"/>
+                        <xsd:attribute type="xsd:string" name="pdx-class" use="optional"/>
+                        <xsd:attribute type="xsd:string" name="primary-key-in-value" use="optional"/>
+                    </xsd:complexType>
+                </xsd:element>
+            </xsd:sequence>
+        </xsd:complexType>
+    </xsd:element>
+</xsd:schema>
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfigurationTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfigurationTest.java
index 639e278..a563d64 100644
--- a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfigurationTest.java
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/ConnectionConfigurationTest.java
@@ -16,6 +16,9 @@ package org.apache.geode.connectors.jdbc.internal;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
@@ -26,7 +29,7 @@ public class ConnectionConfigurationTest {
 
   @Test
   public void initiatedWithNullValues() {
-    ConnectionConfiguration config = new ConnectionConfiguration(null, null, null, null);
+    ConnectionConfiguration config = new ConnectionConfiguration(null, null, null, null, null);
     assertThat(config.getName()).isNull();
     assertThat(config.getUrl()).isNull();
     assertThat(config.getUser()).isNull();
@@ -36,28 +39,37 @@ public class ConnectionConfigurationTest {
   @Test
   public void hasCorrectName() {
     String name = "name";
-    ConnectionConfiguration config = new ConnectionConfiguration(name, null, null, null);
+    ConnectionConfiguration config = new ConnectionConfiguration(name, null, null, null, null);
     assertThat(config.getName()).isEqualTo(name);
   }
 
   @Test
   public void hasCorrectUrl() {
     String url = "url";
-    ConnectionConfiguration config = new ConnectionConfiguration(null, url, null, null);
+    ConnectionConfiguration config = new ConnectionConfiguration(null, url, null, null, null);
     assertThat(config.getUrl()).isEqualTo(url);
   }
 
   @Test
   public void hasCorrectUser() {
     String user = "user";
-    ConnectionConfiguration config = new ConnectionConfiguration(null, null, user, null);
+    ConnectionConfiguration config = new ConnectionConfiguration(null, null, user, null, null);
     assertThat(config.getUser()).isEqualTo(user);
   }
 
   @Test
   public void hasCorrectPassword() {
     String password = "password";
-    ConnectionConfiguration config = new ConnectionConfiguration(null, null, null, password);
+    ConnectionConfiguration config = new ConnectionConfiguration(null, null, null, password, null);
     assertThat(config.getPassword()).isEqualTo(password);
   }
+
+  @Test
+  public void hasCorrectProperties() {
+    Map<String, String> parameters = new HashMap<>();
+    parameters.put("param1", "value1");
+    parameters.put("param2", "value2");
+    ConnectionConfiguration config = new ConnectionConfiguration(null, null, "username", "secret", parameters);
+    assertThat(config.getConnectionProperties()).containsEntry("user", "username").containsEntry("password", "secret").containsEntry("param1", "value1").containsEntry("param2", "value2");
+  }
 }
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/ConnectionManagerUnitTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/ConnectionManagerUnitTest.java
index 8358d1c..b6b4a67 100644
--- a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/ConnectionManagerUnitTest.java
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/ConnectionManagerUnitTest.java
@@ -233,7 +233,7 @@ public class ConnectionManagerUnitTest {
 
   private ConnectionConfiguration getTestConnectionConfig(String name, String url, String user,
       String password) {
-    ConnectionConfiguration config = new ConnectionConfiguration(name, url, user, password);
+    ConnectionConfiguration config = new ConnectionConfiguration(name, url, user, password, null);
     return config;
   }
 
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/TestConfigService.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/TestConfigService.java
index b387d72..15370f4 100644
--- a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/TestConfigService.java
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/TestConfigService.java
@@ -49,6 +49,6 @@ public class TestConfigService {
   }
 
   private static ConnectionConfiguration createConnectionConfig() {
-    return new ConnectionConfiguration(CONNECTION_CONFIG_NAME, CONNECTION_URL, null, null);
+    return new ConnectionConfiguration(CONNECTION_CONFIG_NAME, CONNECTION_URL, null, null, null);
   }
 }
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommandDUnitTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommandDUnitTest.java
new file mode 100644
index 0000000..f58a33b
--- /dev/null
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommandDUnitTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.connectors.jdbc.internal.cli;
+
+import static org.apache.geode.connectors.jdbc.internal.cli.CreateConnectionCommand.CREATE_CONNECTION;
+import static org.apache.geode.connectors.jdbc.internal.cli.CreateConnectionCommand.CREATE_CONNECTION__NAME;
+import static org.apache.geode.connectors.jdbc.internal.cli.CreateConnectionCommand.CREATE_CONNECTION__PARAMS;
+import static org.apache.geode.connectors.jdbc.internal.cli.CreateConnectionCommand.CREATE_CONNECTION__PASSWORD;
+import static org.apache.geode.connectors.jdbc.internal.cli.CreateConnectionCommand.CREATE_CONNECTION__URL;
+import static org.apache.geode.connectors.jdbc.internal.cli.CreateConnectionCommand.CREATE_CONNECTION__USER;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
+import org.apache.geode.connectors.jdbc.internal.InternalJdbcConnectorService;
+import org.apache.geode.distributed.internal.InternalLocator;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.internal.cli.util.CommandStringBuilder;
+import org.apache.geode.test.dunit.rules.LocatorServerStartupRule;
+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.serializable.SerializableTestName;
+
+@Category(DistributedTest.class)
+public class CreateConnectionCommandDUnitTest {
+
+  @Rule
+  public transient GfshCommandRule gfsh = new GfshCommandRule();
+
+  @Rule
+  public LocatorServerStartupRule startupRule = new LocatorServerStartupRule();
+
+  @Rule
+  public SerializableTestName testName = new SerializableTestName();
+
+  private MemberVM locator;
+  private MemberVM server;
+
+  @Before
+  public void before() throws Exception {
+    locator = startupRule.startLocatorVM(0);
+    server = startupRule.startServerVM(1, locator.getPort());
+
+    gfsh.connectAndVerify(locator);
+  }
+
+  @Test
+  public void createsConnection() throws Exception {
+    CommandStringBuilder csb = new CommandStringBuilder(CREATE_CONNECTION);
+    csb.addOption(CREATE_CONNECTION__NAME, "name");
+    csb.addOption(CREATE_CONNECTION__URL, "url");
+    csb.addOption(CREATE_CONNECTION__USER, "username");
+    csb.addOption(CREATE_CONNECTION__PASSWORD, "secret");
+    csb.addOption(CREATE_CONNECTION__PARAMS, "param1:value1,param2:value2");
+
+    gfsh.executeAndAssertThat(csb.toString())
+        .statusIsSuccess();
+
+    locator.invoke(() -> {String xml = InternalLocator.getLocator().getSharedConfiguration().getConfiguration("cluster").getCacheXmlContent();
+      assertThat(xml).contains("jdbc:connector-service");
+    });
+
+    server.invoke(() -> {
+      InternalCache cache = LocatorServerStartupRule.getCache();
+      ConnectionConfiguration config = cache.getService(InternalJdbcConnectorService.class).getConnectionConfig("name");
+      assertThat(config.getUrl()).isEqualTo("url");
+      assertThat(config.getUser()).isEqualTo("username");
+      assertThat(config.getPassword()).isEqualTo("secret");
+      assertThat(config.getConnectionProperties()).containsEntry("param1", "value1").containsEntry("param2", "value2");
+    });
+  }
+}
\ No newline at end of file
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommandIntegrationTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommandIntegrationTest.java
new file mode 100644
index 0000000..fbc5e78
--- /dev/null
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionCommandIntegrationTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geode.connectors.jdbc.internal.cli;
+
+import static org.apache.geode.distributed.ConfigurationProperties.ENABLE_CLUSTER_CONFIGURATION;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.CacheFactory;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
+import org.apache.geode.connectors.jdbc.internal.InternalJdbcConnectorService;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.cli.Result;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+@Category(IntegrationTest.class)
+public class CreateConnectionCommandIntegrationTest {
+
+  private InternalCache cache;
+  private CreateConnectionCommand createConnectionCommand;
+
+  private String name;
+  private String url;
+  private String user;
+  private String password;
+  private String[] params;
+
+  @Before
+  public void setup() throws Exception {
+    cache = (InternalCache) new CacheFactory().set(ENABLE_CLUSTER_CONFIGURATION, "true").create();
+    createConnectionCommand = new CreateConnectionCommand();
+
+    name = "name";
+    url = "url";
+    user = "user";
+    password = "password";
+    params = new String[] { "param1:value1", "param2:value2" };
+  }
+
+  @After
+  public void tearDown() {
+    cache.close();
+  }
+
+  @Test
+  public void createsConnectionConfigurationInService() throws Exception {
+    Result result = createConnectionCommand.createConnection(name, url, user, password, params);
+
+    assertThat(result.getStatus()).isSameAs(Result.Status.OK);
+
+    InternalJdbcConnectorService service = cache.getService(InternalJdbcConnectorService.class);
+    ConnectionConfiguration connectionConfig = service.getConnectionConfig(name);
+
+    assertThat(connectionConfig).isNotNull();
+    assertThat(connectionConfig.getName()).isEqualTo(name);
+    assertThat(connectionConfig.getUrl()).isEqualTo(url);
+    assertThat(connectionConfig.getUser()).isEqualTo(user);
+    assertThat(connectionConfig.getPassword()).isEqualTo(password);
+    assertThat(connectionConfig.getConnectionProperties()).containsEntry("param1", "value1").containsEntry("param2", "value2");
+  }
+}
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionFunctionTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionFunctionTest.java
new file mode 100644
index 0000000..6a4f823
--- /dev/null
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/cli/CreateConnectionFunctionTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.connectors.jdbc.internal.cli;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.cache.execute.ResultSender;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfigBuilder;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
+import org.apache.geode.connectors.jdbc.internal.InternalJdbcConnectorService;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.distributed.DistributedSystem;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.test.junit.categories.UnitTest;
+
+@Category(UnitTest.class)
+public class CreateConnectionFunctionTest {
+
+  private CreateConnectionFunction function;
+  private FunctionContext<ConnectionConfiguration> context;
+  private ResultSender<Object> resultSender;
+  private InternalCache cache;
+  private DistributedSystem system;
+  private DistributedMember member;
+  private ConnectionConfiguration configuration;
+  private InternalJdbcConnectorService service;
+
+  @Before
+  public void setup() {
+    configuration = new ConnectionConfigBuilder().build();
+
+    context = mock(FunctionContext.class);
+    resultSender = mock(ResultSender.class);
+    cache = mock(InternalCache.class);
+    system = mock(DistributedSystem.class);
+    member = mock(DistributedMember.class);
+    service = mock(InternalJdbcConnectorService.class);
+
+    when(context.getResultSender()).thenReturn(resultSender);
+    when(context.getCache()).thenReturn(cache);
+    when(cache.getDistributedSystem()).thenReturn(system);
+    when(system.getDistributedMember()).thenReturn(member);
+    when(context.getArguments()).thenReturn(configuration);
+    when(cache.getService(eq(InternalJdbcConnectorService.class))).thenReturn(service);
+
+    function = new CreateConnectionFunction();
+  }
+
+  @Test
+  public void isHAIsFalse() throws Exception {
+    assertThat(function.isHA()).isFalse();
+  }
+
+  @Test
+  public void getId() throws Exception {
+    assertThat(function.getId()).isEqualTo(function.getClass().getName());
+  }
+
+  @Test
+  public void execute() throws Exception {
+    function.execute(context);
+    verify(service, times(1)).addOrUpdateConnectionConfig(configuration);
+  }
+
+}
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/ConnectionConfigBuilderTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/ConnectionConfigBuilderTest.java
index e97ecc1..b1de311 100644
--- a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/ConnectionConfigBuilderTest.java
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/ConnectionConfigBuilderTest.java
@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfigBuilder;
 import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
 import org.apache.geode.test.junit.categories.UnitTest;
 
@@ -36,11 +37,12 @@ public class ConnectionConfigBuilderTest {
   @Test
   public void createsObjectWithCorrectValues() {
     ConnectionConfigBuilder builder = new ConnectionConfigBuilder();
-    builder.withName("name").withUrl("url").withUser("user").withPassword("password");
+    builder.withName("name").withUrl("url").withUser("user").withPassword("password").withParameters(new String[] {"param1:value1", "param2:value2"});
     ConnectionConfiguration config = builder.build();
     assertThat(config.getName()).isEqualTo("name");
     assertThat(config.getUrl()).isEqualTo("url");
     assertThat(config.getUser()).isEqualTo("user");
     assertThat(config.getPassword()).isEqualTo("password");
+    assertThat(config.getConnectionProperties()).containsEntry("param1", "value1").containsEntry("param2", "value2");
   }
 }
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/ElementTypeTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/ElementTypeTest.java
index 8c290b7..4877ac9 100644
--- a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/ElementTypeTest.java
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/ElementTypeTest.java
@@ -43,6 +43,7 @@ import org.xml.sax.Attributes;
 
 import org.apache.geode.cache.Cache;
 import org.apache.geode.cache.CacheXmlException;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfigBuilder;
 import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
 import org.apache.geode.connectors.jdbc.internal.RegionMapping;
 import org.apache.geode.internal.cache.extension.ExtensionPoint;
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlGeneratorIntegrationTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlGeneratorIntegrationTest.java
index 5b6d260..252aab7 100644
--- a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlGeneratorIntegrationTest.java
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlGeneratorIntegrationTest.java
@@ -27,6 +27,7 @@ import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorService
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.TABLE;
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.URL;
 import static org.apache.geode.connectors.jdbc.internal.xml.JdbcConnectorServiceXmlParser.USER;
+import static org.apache.geode.distributed.ConfigurationProperties.CACHE_XML_FILE;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.fail;
 
@@ -34,10 +35,14 @@ import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.file.Files;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
 
 import org.junit.After;
 import org.junit.Before;
@@ -51,29 +56,36 @@ import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
 import org.apache.geode.cache.CacheFactory;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfigBuilder;
 import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
 import org.apache.geode.connectors.jdbc.internal.InternalJdbcConnectorService;
 import org.apache.geode.connectors.jdbc.internal.RegionMapping;
 import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.cache.xmlcache.CacheXml;
 import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator;
+import org.apache.geode.internal.cache.xmlcache.CacheXmlParser;
+import org.apache.geode.management.internal.configuration.utils.XmlUtils;
 import org.apache.geode.test.junit.categories.IntegrationTest;
 
 @Category(IntegrationTest.class)
 public class JdbcConnectorServiceXmlGeneratorIntegrationTest {
 
   private InternalCache cache;
+  private File cacheXmlFile;
 
   @Rule
   public TemporaryFolder temporaryFolder = new TemporaryFolder();
 
   @Before
-  public void setup() {
+  public void setup() throws IOException {
     cache = (InternalCache) new CacheFactory().create();
+    cacheXmlFile = temporaryFolder.newFile("cache.xml");
   }
 
   @After
   public void tearDown() {
     cache.close();
+    cache = null;
   }
 
   @Test
@@ -149,6 +161,97 @@ public class JdbcConnectorServiceXmlGeneratorIntegrationTest {
     validatePresenceOfFieldMapping(fieldMappingElements, "fieldName2", "columnMapping2");
   }
 
+  @Test
+  public void generatedXmlWithConnectionConfigurationCanBeParsed() throws Exception {
+    InternalJdbcConnectorService service = cache.getService(InternalJdbcConnectorService.class);
+    ConnectionConfiguration config = new ConnectionConfigBuilder().withName("name").withUrl("url")
+        .withUser("username").withPassword("secret").build();
+    service.addOrUpdateConnectionConfig(config);
+    generateXml();
+
+    cache.close();
+    cache = (InternalCache) new CacheFactory().set(CACHE_XML_FILE, cacheXmlFile.getAbsolutePath()).create();
+
+    service = cache.getService(InternalJdbcConnectorService.class);
+
+    assertThat(service.getConnectionConfig("name")).isEqualTo(config);
+  }
+
+  @Test
+  public void generatedXmlWithConnectionConfigurationCanBeXPathed() throws Exception {
+    InternalJdbcConnectorService service = cache.getService(InternalJdbcConnectorService.class);
+    ConnectionConfiguration config = new ConnectionConfigBuilder().withName("name").withUrl("url")
+        .withUser("username").withPassword("secret").build();
+    service.addOrUpdateConnectionConfig(config);
+    generateXml();
+
+    for(String line : Files.readAllLines(cacheXmlFile.toPath())) {
+      System.out.println(line);
+    }
+
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    factory.setNamespaceAware(true);
+
+    DocumentBuilder builder = factory.newDocumentBuilder();
+    builder.setEntityResolver(new CacheXmlParser());
+
+    Document document = builder.parse(cacheXmlFile);
+    System.out.println(document.getDocumentElement());
+    XmlUtils.XPathContext xpathContext = new XmlUtils.XPathContext();
+    xpathContext.addNamespace(CacheXml.PREFIX, CacheXml.GEODE_NAMESPACE);
+    xpathContext.addNamespace(PREFIX, NAMESPACE); // TODO: wrap this line with conditional
+    // Create an XPathContext here
+    XPath xpath = XPathFactory.newInstance().newXPath();
+    xpath.setNamespaceContext(xpathContext);
+    Object result = xpath.evaluate("//cache/jdbc:connector-service", document, XPathConstants.NODE);
+    //Node element = XmlUtils.querySingleElement(document, "//cache/jdbc:connector-service", xpathContext);
+    // Must copy to preserve namespaces.
+    System.out.println("RESULT = " + XmlUtils.elementToString((Element) result));
+  }
+
+
+  @Test
+  public void generatedXmlWithRegionMappingCanBeParsed() throws Exception {
+    InternalJdbcConnectorService service = cache.getService(InternalJdbcConnectorService.class);
+    RegionMapping mapping = new RegionMappingBuilder().withRegionName("region").withPdxClassName("class")
+        .withTableName("table").withConnectionConfigName("connection").withPrimaryKeyInValue(true)
+        .withFieldToColumnMapping("field1", "columnMapping1")
+        .withFieldToColumnMapping("field2", "columnMapping2")
+        .build();
+    service.addOrUpdateRegionMapping(mapping);
+    generateXml();
+
+    cache.close();
+    cache = (InternalCache) new CacheFactory().set(CACHE_XML_FILE, cacheXmlFile.getAbsolutePath()).create();
+
+    service = cache.getService(InternalJdbcConnectorService.class);
+
+    assertThat(service.getMappingForRegion("region")).isEqualTo(mapping);
+  }
+
+  @Test
+  public void generatedXmlWithEverythingCanBeParsed() throws Exception {
+    InternalJdbcConnectorService service = cache.getService(InternalJdbcConnectorService.class);
+    ConnectionConfiguration config = new ConnectionConfigBuilder().withName("name").withUrl("url")
+        .withUser("username").withPassword("secret").build();
+    service.addOrUpdateConnectionConfig(config);
+    RegionMapping mapping = new RegionMappingBuilder().withRegionName("region").withPdxClassName("class")
+        .withTableName("table").withConnectionConfigName("connection").withPrimaryKeyInValue(true)
+        .withFieldToColumnMapping("field1", "columnMapping1")
+        .withFieldToColumnMapping("field2", "columnMapping2")
+        .build();
+    service.addOrUpdateRegionMapping(mapping);
+    generateXml();
+
+    cache.close();
+    cache = (InternalCache) new CacheFactory().set(CACHE_XML_FILE, cacheXmlFile.getAbsolutePath()).create();
+
+    service = cache.getService(InternalJdbcConnectorService.class);
+
+    assertThat(service.getConnectionConfig("name")).isEqualTo(config);
+    assertThat(service.getMappingForRegion("region")).isEqualTo(mapping);
+  }
+
   private void validatePresenceOfFieldMapping(NodeList elements, String fieldName,
       String columnName) {
     for (int i = 0; i < elements.getLength(); i++) {
@@ -177,19 +280,18 @@ public class JdbcConnectorServiceXmlGeneratorIntegrationTest {
 
   private Document getCacheXmlDocument()
       throws IOException, SAXException, ParserConfigurationException {
-    File cacheXml = new File(temporaryFolder.getRoot(), "cache.xml");
     DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+    dbFactory.setNamespaceAware(false);
+    dbFactory.setValidating(false);
     DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
-    Document document = dBuilder.parse(cacheXml);
+    Document document = dBuilder.parse(cacheXmlFile);
     document.getDocumentElement().normalize();
     return document;
   }
 
   private void generateXml() throws IOException {
-    File cacheXml = new File(temporaryFolder.getRoot(), "cache.xml");
-    PrintWriter printWriter = new PrintWriter(new FileWriter(cacheXml));
+    PrintWriter printWriter = new PrintWriter(new FileWriter(cacheXmlFile));
     CacheXmlGenerator.generate(cache, printWriter, true, false, false);
     printWriter.flush();
   }
-
 }
diff --git a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlIntegrationTest.java b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlIntegrationTest.java
index ac7fcf5..9693201 100644
--- a/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlIntegrationTest.java
+++ b/geode-connectors/src/test/java/org/apache/geode/connectors/jdbc/internal/xml/JdbcConnectorServiceXmlIntegrationTest.java
@@ -30,6 +30,7 @@ import org.junit.experimental.categories.Category;
 import org.junit.rules.TemporaryFolder;
 
 import org.apache.geode.cache.CacheFactory;
+import org.apache.geode.connectors.jdbc.internal.ConnectionConfigBuilder;
 import org.apache.geode.connectors.jdbc.internal.ConnectionConfiguration;
 import org.apache.geode.connectors.jdbc.internal.InternalJdbcConnectorService;
 import org.apache.geode.connectors.jdbc.internal.JdbcConnectorService;
@@ -82,7 +83,7 @@ public class JdbcConnectorServiceXmlIntegrationTest {
   private void configureService() {
     InternalJdbcConnectorService service = cache.getService(InternalJdbcConnectorService.class);
     config1 = new ConnectionConfigBuilder().withName("connection1").withUrl("url1")
-        .withUser("username1").withPassword("secret1").build();
+        .withUser("username1").withPassword("secret1").withParameters(new String[]{"param1:value1", "param2:value2"}).build();
     config2 = new ConnectionConfigBuilder().withName("connection2").withUrl("url2")
         .withUser("username2").withPassword("secret2").build();
     service.addOrUpdateConnectionConfig(config1);
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/XmlEntity.java b/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/XmlEntity.java
index fbb30cc..db370a2 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/XmlEntity.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/configuration/domain/XmlEntity.java
@@ -148,6 +148,41 @@ public class XmlEntity implements VersionedDataSerializable {
     initializeSearchString(parentKey, parentValue, childPrefix, childKey, childValue);
   }
 
+   public XmlEntity(final String parentType, final String childPrefix, final String childNamespace, final String childType) {
+     this.parentType = parentType;
+     this.type = childType;
+     this.childPrefix = childPrefix;
+     this.childNamespace = childNamespace;
+
+     StringBuilder sb = new StringBuilder();
+     sb.append("//").append(this.parentType);
+     sb.append("/").append(childPrefix).append(':').append(this.type);
+     this.searchString = sb.toString();
+     this.xmlDefinition = parseXmlForDefinition();
+   }
+
+  private String parseXmlForDefinition() {
+    final Cache cache = CacheFactory.getAnyInstance();
+
+    final StringWriter stringWriter = new StringWriter();
+    final PrintWriter printWriter = new PrintWriter(stringWriter);
+    CacheXmlGenerator.generate(cache, printWriter, true, false, false);
+    printWriter.close();
+    InputSource inputSource = new InputSource(new StringReader(stringWriter.toString()));
+    try {
+      Document document = XmlUtils.getDocumentBuilder().parse(inputSource);
+      Node element = document.getElementsByTagNameNS(childNamespace, type).item(0);
+      if (null != element) {
+        return XmlUtils.elementToString(element);
+      }
+    } catch (Exception e) {
+      throw new InternalGemFireError("Could not parse XML when creating XMLEntity", e);
+    }
+    logger.warn("No XML definition could be found with name={} and attributes={}", type,
+        attributes);
+    return null;
+  }
+
   private void initializeSearchString(final String parentKey, final String parentValue,
       final String childPrefix, final String childKey, final String childValue) {
     StringBuffer sb = new StringBuffer();
@@ -206,7 +241,6 @@ public class XmlEntity implements VersionedDataSerializable {
    * @since GemFire 8.1
    */
   private String loadXmlDefinition(final String xmlDocument) {
-    final Cache cache = CacheFactory.getAnyInstance();
     try {
       InputSource inputSource = new InputSource(new StringReader(xmlDocument));
       return loadXmlDefinition(XmlUtils.getDocumentBuilder().parse(inputSource));
@@ -226,16 +260,15 @@ public class XmlEntity implements VersionedDataSerializable {
    * @throws TransformerFactoryConfigurationError
    * @since GemFire 8.1
    */
-  private String loadXmlDefinition(final Document document)
+  public String loadXmlDefinition(final Document document)
       throws XPathExpressionException, TransformerFactoryConfigurationError, TransformerException {
-    final Cache cache = CacheFactory.getAnyInstance();
-
     this.searchString = createQueryString(prefix, type, attributes);
     logger.info("XmlEntity:searchString: {}", this.searchString);
 
     if (document != null) {
       XPathContext xpathContext = new XPathContext();
       xpathContext.addNamespace(prefix, namespace);
+      xpathContext.addNamespace(childPrefix, childNamespace); // TODO: wrap this line with conditional
       // Create an XPathContext here
       Node element = XmlUtils.querySingleElement(document, this.searchString, xpathContext);
       // Must copy to preserve namespaces.

-- 
To stop receiving notification emails like this one, please contact
"commits@geode.apache.org" <co...@geode.apache.org>.