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:30 UTC

[geode] branch feature/GEODE-3824 updated (39bc419 -> a54832f)

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

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


 discard 39bc419  Remove uneede class
 discard 8d24c99  Add support for connection parameters
 discard 665f21b  fix xml parsing
 discard 1147fe2  WIP XPath usage is broken see XPathTest
 discard 1ef4d12  WIP xml testing still failing but builds cleanly
     new a54832f  GEODE-3824: Allow JDBC connection configuration for gfsh

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (39bc419)
            \
             N -- N -- N   refs/heads/feature/GEODE-3824 (a54832f)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:

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

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

Posted by nr...@apache.org.
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>.