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

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


The following commit(s) were added to refs/heads/feature/GEODE-5951 by this 
push:
     new ccef399  wip: create data-source command added and it seems to work. 
The most recent change worked on was the --properties option. It now parses 
just name:value pairs (no more type) but a TODO exists for it on 
CreateDataSourceCommand and that file currently does not compile. More tests 
need to be added for this new command.
ccef399 is described below

commit ccef399aea8f2808fbe5c0b2e69a7b6d1b2d6535
Author: Darrel Schneider <[email protected]>
AuthorDate: Tue Oct 30 08:40:21 2018 -0700

    wip: create data-source command added and it seems to work.
    The most recent change worked on was the --properties option.
    It now parses just name:value pairs (no more type) but a TODO
    exists for it on CreateDataSourceCommand and that file currently
    does not compile.
    More tests need to be added for this new command.
---
 .../commands/CreateDataSourceCommandDUnitTest.java | 125 +++++++++++++++
 .../cache/configuration/JndiBindingsType.java      |  17 ++
 .../cli/commands/CreateDataSourceCommand.java      | 176 +++++++++++++++++++++
 .../converters/DataSourcePropertyConverter.java    |  60 +++++++
 .../cli/functions/CreateJndiBindingFunction.java   |  14 +-
 5 files changed, 388 insertions(+), 4 deletions(-)

diff --git 
a/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/CreateDataSourceCommandDUnitTest.java
 
b/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/CreateDataSourceCommandDUnitTest.java
new file mode 100644
index 0000000..0dcc960
--- /dev/null
+++ 
b/geode-core/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/CreateDataSourceCommandDUnitTest.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional 
information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache 
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the 
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
+ * or implied. See the License for the specific language governing permissions 
and limitations under
+ * the License.
+ */
+
+package org.apache.geode.management.internal.cli.commands;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import 
org.apache.geode.distributed.internal.InternalConfigurationPersistenceService;
+import org.apache.geode.internal.jndi.JNDIInvoker;
+import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.management.internal.configuration.domain.Configuration;
+import org.apache.geode.management.internal.configuration.utils.XmlUtils;
+import org.apache.geode.test.dunit.IgnoredException;
+import org.apache.geode.test.dunit.SerializableRunnableIF;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.rules.GfshCommandRule;
+import org.apache.geode.test.junit.rules.VMProvider;
+
+public class CreateDataSourceCommandDUnitTest {
+
+  private static MemberVM locator, server1, server2;
+
+  @ClassRule
+  public static ClusterStartupRule cluster = new ClusterStartupRule();
+
+  @ClassRule
+  public static GfshCommandRule gfsh = new GfshCommandRule();
+
+
+  @BeforeClass
+  public static void before() throws Exception {
+    locator = cluster.startLocatorVM(0);
+    server1 = cluster.startServerVM(1, "group1", locator.getPort());
+    server2 = cluster.startServerVM(2, "group1", locator.getPort());
+
+    gfsh.connectAndVerify(locator);
+  }
+
+  @Test
+  public void testCreateDataSource() throws Exception {
+    VMProvider.invokeInEveryMember(
+        () -> 
assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(0));
+
+    // create the data-source
+    gfsh.executeAndAssertThat(
+        "create data-source --name=jndi1 --username=myuser --password=mypass 
--pooled=false --url=\"jdbc:derby:newDB;create=true\" 
--properties={'name':'name1','value':'value1'},{'name':'name2','value':'value2'}")
+        .statusIsSuccess().tableHasColumnOnlyWithValues("Member", "server-1", 
"server-2");
+
+    // verify cluster config is updated
+    locator.invoke(() -> {
+      InternalConfigurationPersistenceService ccService =
+          ClusterStartupRule.getLocator().getConfigurationPersistenceService();
+      Configuration configuration = ccService.getConfiguration("cluster");
+      String xmlContent = configuration.getCacheXmlContent();
+
+      Document document = XmlUtils.createDocumentFromXml(xmlContent);
+      NodeList jndiBindings = document.getElementsByTagName("jndi-binding");
+
+      assertThat(jndiBindings.getLength()).isEqualTo(1);
+      assertThat(xmlContent).contains("user-name=\"myuser\"");
+      assertThat(xmlContent).contains("password=\"mypass\"");
+
+      boolean found = false;
+      for (int i = 0; i < jndiBindings.getLength(); i++) {
+        Element eachBinding = (Element) jndiBindings.item(i);
+        if (eachBinding.getAttribute("jndi-name").equals("jndi1")) {
+          found = true;
+          break;
+        }
+      }
+      assertThat(found).isTrue();
+    });
+
+    // verify datasource exists
+    VMProvider.invokeInEveryMember(
+        () -> 
assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(1));
+
+    // bounce server1
+    server1.stop(false);
+    server1 = cluster.startServerVM(1, locator.getPort());
+
+    // verify it has recreated the datasource from cluster config
+    server1.invoke(() -> {
+      assertThat(JNDIInvoker.getNoOfAvailableDataSources()).isEqualTo(1);
+    });
+
+    verifyThatNonExistentClassCausesGfshToError();
+  }
+
+  private void verifyThatNonExistentClassCausesGfshToError() {
+    SerializableRunnableIF IgnoreClassNotFound = () -> {
+      IgnoredException ex =
+          new IgnoredException("non_existent_class_name");
+      LogService.getLogger().info(ex.getAddMessage());
+    };
+
+    server1.invoke(IgnoreClassNotFound);
+    server2.invoke(IgnoreClassNotFound);
+
+    // create the binding
+    gfsh.executeAndAssertThat(
+        "create data-source --name=jndiBad --username=myuser --password=mypass 
--pooled --pooled-data-source-factory-class=non_existent_class_name 
--url=\"jdbc:derby:newDB;create=true\"")
+        .statusIsError();
+  }
+}
diff --git 
a/geode-core/src/main/java/org/apache/geode/cache/configuration/JndiBindingsType.java
 
b/geode-core/src/main/java/org/apache/geode/cache/configuration/JndiBindingsType.java
index 044a7f6..aa58456 100644
--- 
a/geode-core/src/main/java/org/apache/geode/cache/configuration/JndiBindingsType.java
+++ 
b/geode-core/src/main/java/org/apache/geode/cache/configuration/JndiBindingsType.java
@@ -25,6 +25,7 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
 
 import org.apache.geode.annotations.Experimental;
@@ -237,6 +238,9 @@ public class JndiBindingsType {
     @XmlAttribute(name = "xa-datasource-class")
     protected String xaDatasourceClass;
 
+    @XmlTransient
+    private boolean createdByDataSourceCommand;
+
     /**
      * Gets the value of the configProperties property.
      *
@@ -602,6 +606,14 @@ public class JndiBindingsType {
       return getJndiName();
     }
 
+    public boolean isCreatedByDataSourceCommand() {
+      return createdByDataSourceCommand;
+    }
+
+    public void setCreatedByDataSourceCommand(boolean 
createdByDataSourceCommand) {
+      this.createdByDataSourceCommand = createdByDataSourceCommand;
+    }
+
 
     /**
      * <p>
@@ -649,6 +661,11 @@ public class JndiBindingsType {
         this.configPropertyValue = value;
       }
 
+      public ConfigProperty(String name, String value) {
+        this.configPropertyName = name;
+        this.configPropertyValue = value;
+      }
+
       /**
        * Get the id of the element. The id is the same as the name.
        *
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateDataSourceCommand.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateDataSourceCommand.java
new file mode 100644
index 0000000..afe48ef
--- /dev/null
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/CreateDataSourceCommand.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional 
information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache 
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the 
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
+ * or implied. See the License for the specific language governing permissions 
and limitations under
+ * the License.
+ */
+package org.apache.geode.management.internal.cli.commands;
+
+import java.util.List;
+import java.util.Set;
+
+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.configuration.CacheConfig;
+import org.apache.geode.cache.configuration.CacheElement;
+import org.apache.geode.cache.configuration.JndiBindingsType;
+import org.apache.geode.distributed.DistributedMember;
+import 
org.apache.geode.distributed.internal.InternalConfigurationPersistenceService;
+import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.management.cli.CliMetaData;
+import org.apache.geode.management.cli.SingleGfshCommand;
+import 
org.apache.geode.management.internal.cli.commands.CreateJndiBindingCommand.DATASOURCE_TYPE;
+import 
org.apache.geode.management.internal.cli.exceptions.EntityExistsException;
+import org.apache.geode.management.internal.cli.functions.CliFunctionResult;
+import 
org.apache.geode.management.internal.cli.functions.CreateJndiBindingFunction;
+import org.apache.geode.management.internal.cli.i18n.CliStrings;
+import org.apache.geode.management.internal.cli.result.model.ResultModel;
+import org.apache.geode.management.internal.security.ResourceOperation;
+import org.apache.geode.security.ResourcePermission;
+
+public class CreateDataSourceCommand extends SingleGfshCommand {
+  private static final Logger logger = LogService.getLogger();
+
+  static final String CREATE_DATA_SOURCE = "create data-source";
+  static final String CREATE_DATA_SOURCE__HELP = "Create a JDBC data source.";
+  static final String POOLED_DATA_SOURCE_FACTORY_CLASS = 
"pooled-data-source-factory-class";
+  static final String POOLED_DATA_SOURCE_FACTORY_CLASS__HELP =
+      "This class will be created, by calling its no-arg constructor, and used 
as the pool of this data source. "
+          + " The class must implement 
org.apache.geode.datasource.PooledDataSourceFactory.";
+  static final String URL = "url";
+  static final String URL__HELP =
+      "This is the JDBC data source URL string, for example, 
jdbc:hsqldb:hsql://localhost:1701.";
+  static final String NAME = "name";
+  static final String NAME__HELP = "Name of the data source to be created.";
+  static final String PASSWORD = "password";
+  static final String PASSWORD__HELP =
+      "This element specifies the default password used when creating a new 
connection.";
+  static final String USERNAME = "username";
+  static final String USERNAME__HELP =
+      "This element specifies the default username used when creating a new 
connection.";
+  static final String POOLED = "pooled";
+  static final String POOLED__HELP =
+      "By default a pooled data source is created. If this option is false 
then a non-pooled data source is created.";
+  static final String IFNOTEXISTS__HELP =
+      "Skip the create operation when a data source with the same name already 
exists.  Without specifying this option, this command execution results into an 
error.";
+  static final String PROPERTIES = "properties";
+  static final String PROPERTIES_HELP =
+      "Properties for the data source. When used with a pooled data source, 
these properties will be used to configure the database data source unless the 
name begins with \"pool.\"."
+          + " If that prefix is used it will be used to configure the pool 
data source. For non-pooled data sources, these properties are just passed to 
the database data source. "
+          + "The value is a comma separated list of json strings. Each json 
string contains a name and value. "
+          + "For example: 
--properties={'name':'name1','value':'value1'},{'name':'name2','value':'value2'}";
+
+  @CliCommand(value = CREATE_DATA_SOURCE, help = CREATE_DATA_SOURCE__HELP)
+  @CliMetaData(relatedTopic = CliStrings.TOPIC_GEODE_REGION,
+      interceptor = 
"org.apache.geode.management.internal.cli.commands.UsernamePasswordInterceptor")
+  @ResourceOperation(resource = ResourcePermission.Resource.CLUSTER,
+      operation = ResourcePermission.Operation.MANAGE)
+  public ResultModel createJDNIBinding(
+      @CliOption(key = POOLED_DATA_SOURCE_FACTORY_CLASS,
+          help = POOLED_DATA_SOURCE_FACTORY_CLASS__HELP) String 
connectionPooledDatasource,
+      @CliOption(key = URL, mandatory = true,
+          help = URL__HELP) String url,
+      @CliOption(key = NAME, mandatory = true, help = NAME__HELP) String 
jndiName,
+      @CliOption(key = USERNAME, help = USERNAME__HELP) String username,
+      @CliOption(key = PASSWORD, help = PASSWORD__HELP) String password,
+      @CliOption(key = CliStrings.IFNOTEXISTS, help = IFNOTEXISTS__HELP,
+          specifiedDefaultValue = "true", unspecifiedDefaultValue = "false") 
boolean ifNotExists,
+      @CliOption(key = POOLED, help = POOLED__HELP,
+          specifiedDefaultValue = "true", unspecifiedDefaultValue = "true") 
boolean pooled,
+      @CliOption(key = PROPERTIES, optionContext = 
"splittingRegex=,(?![^{]*\\})",
+          help = PROPERTIES_HELP) DataSourceProperty[] properties) {
+
+    JndiBindingsType.JndiBinding configuration = new 
JndiBindingsType.JndiBinding();
+    configuration.setCreatedByDataSourceCommand(true);
+    configuration.setConnPooledDatasourceClass(connectionPooledDatasource);
+    configuration.setConnectionUrl(url);
+    configuration.setJndiName(jndiName);
+    configuration.setPassword(password);
+    if (pooled) {
+      configuration.setType(DATASOURCE_TYPE.POOLED.getType());
+    } else {
+      configuration.setType(DATASOURCE_TYPE.SIMPLE.getType());
+    }
+    configuration.setUserName(username);
+    if (properties != null && properties.length > 0) {
+      // TODO convert DataSourceProperty to ConfigProperty
+      configuration.getConfigProperties().addAll(Arrays.asList(properties));
+    }
+
+    InternalConfigurationPersistenceService service =
+        (InternalConfigurationPersistenceService) 
getConfigurationPersistenceService();
+
+    if (service != null) {
+      CacheConfig cacheConfig = service.getCacheConfig("cluster");
+      if (cacheConfig != null) {
+        JndiBindingsType.JndiBinding existing =
+            CacheElement.findElement(cacheConfig.getJndiBindings(), jndiName);
+        if (existing != null) {
+          throw new EntityExistsException(
+              CliStrings.format("Data source named \"{0}\" already exists.", 
jndiName),
+              ifNotExists);
+        }
+      }
+    }
+
+    Set<DistributedMember> targetMembers = findMembers(null, null);
+    if (targetMembers.size() > 0) {
+      List<CliFunctionResult> jndiCreationResult = executeAndGetFunctionResult(
+          new CreateJndiBindingFunction(), configuration, targetMembers);
+      ResultModel result = 
ResultModel.createMemberStatusResult(jndiCreationResult);
+      result.setConfigObject(configuration);
+      return result;
+    } else {
+      return ResultModel.createInfo("No members found.");
+    }
+  }
+
+  @Override
+  public void updateClusterConfig(String group, CacheConfig config, Object 
element) {
+    config.getJndiBindings().add((JndiBindingsType.JndiBinding) element);
+  }
+
+  public static class DataSourceProperty {
+    @Override
+    public String toString() {
+      return "DataSourceProperty [name=" + name + ", value=" + value + "]";
+    }
+
+    private String name;
+    private String value;
+
+    public DataSourceProperty() {}
+
+    public DataSourceProperty(String name, String value) {
+      this.name = name;
+      this.value = value;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    public void setValue(String value) {
+      this.value = value;
+    }
+
+  }
+}
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/DataSourcePropertyConverter.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/DataSourcePropertyConverter.java
new file mode 100644
index 0000000..9db292f
--- /dev/null
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/converters/DataSourcePropertyConverter.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license
+ * agreements. See the NOTICE file distributed with this work for additional 
information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache 
License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the 
License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
KIND, either express
+ * or implied. See the License for the specific language governing permissions 
and limitations under
+ * the License.
+ */
+package org.apache.geode.management.internal.cli.converters;
+
+import java.io.IOException;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.shell.core.Completion;
+import org.springframework.shell.core.Converter;
+import org.springframework.shell.core.MethodTarget;
+
+import 
org.apache.geode.management.internal.cli.commands.CreateDataSourceCommand.DataSourceProperty;
+
+/***
+ * Converter for CreateDataSourceCommand's --properties option.
+ *
+ */
+public class DataSourcePropertyConverter
+    implements Converter<DataSourceProperty> {
+
+  private static ObjectMapper mapper = new ObjectMapper();
+  static {
+    mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+  }
+
+  @Override
+  public boolean supports(Class<?> type, String optionContext) {
+    return DataSourceProperty.class.isAssignableFrom(type);
+  }
+
+  @Override
+  public DataSourceProperty convertFromText(String value,
+      Class<?> targetType, String optionContext) {
+    try {
+      return mapper.readValue(value, DataSourceProperty.class);
+    } catch (IOException e) {
+      throw new IllegalArgumentException("invalid json: \"" + value + "\" 
details: " + e);
+    }
+  }
+
+  @Override
+  public boolean getAllPossibleValues(List<Completion> completions, Class<?> 
targetType,
+      String existingData, String optionContext, MethodTarget target) {
+    return false;
+  }
+}
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java
index d9ed342..97ffb2b 100644
--- 
a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/CreateJndiBindingFunction.java
@@ -36,31 +36,37 @@ import 
org.apache.geode.management.internal.cli.i18n.CliStrings;
 public class CreateJndiBindingFunction extends 
CliFunction<JndiBindingsType.JndiBinding> {
 
   private static final Logger logger = LogService.getLogger();
-  private static final String RESULT_MESSAGE = "Created jndi binding \"{0}\" 
on \"{1}\".";
 
   @Override
   public CliFunctionResult 
executeFunction(FunctionContext<JndiBindingsType.JndiBinding> context)
       throws DataSourceCreateException, NamingException {
     ResultSender<Object> resultSender = context.getResultSender();
     JndiBindingsType.JndiBinding configuration = context.getArguments();
+    final String TYPE_NAME;
+    if (configuration.isCreatedByDataSourceCommand()) {
+      TYPE_NAME = "data-source";
+    } else {
+      TYPE_NAME = "jndi-binding";
+    }
     try {
       JNDIInvoker.mapDatasource(getParamsAsMap(configuration),
           convert(configuration.getConfigProperties()));
     } catch (DataSourceCreateException ex) {
       if (logger.isErrorEnabled()) {
-        logger.error("create jndi-binding failed", ex.getWrappedException());
+        logger.error("create " + TYPE_NAME + " failed", 
ex.getWrappedException());
       }
       throw ex;
     } catch (NamingException ex) {
       if (logger.isErrorEnabled()) {
-        logger.error("create jndi-binding failed", ex);
+        logger.error("create " + TYPE_NAME + " failed", ex);
       }
       throw ex;
 
     }
 
     return new CliFunctionResult(context.getMemberName(), true,
-        CliStrings.format(RESULT_MESSAGE, configuration.getJndiName(), 
context.getMemberName()));
+        CliStrings.format("Created " + TYPE_NAME + " \"{0}\" on \"{1}\".",
+            configuration.getJndiName(), context.getMemberName()));
   }
 
   static Map getParamsAsMap(JndiBindingsType.JndiBinding binding) {

Reply via email to