blob: e5f0ae52f3b2e7ec2a6806db1af175738192cfd6 [file] [log] [blame]
/*
* 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.lang.Identifiable.exists;
import java.util.List;
import java.util.Set;
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.apache.geode.annotations.Experimental;
import org.apache.geode.cache.configuration.CacheConfig;
import org.apache.geode.cache.configuration.JndiBindingsType;
import org.apache.geode.cache.configuration.JndiBindingsType.JndiBinding.ConfigProperty;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.internal.InternalConfigurationPersistenceService;
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.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;
@Experimental
public class CreateDataSourceCommand extends SingleGfshCommand {
static final String CREATE_DATA_SOURCE = "create data-source";
static final String CREATE_DATA_SOURCE__HELP = EXPERIMENTAL
+ "Creates a JDBC data source and verifies connectivity to an external JDBC database.";
static final String POOLED_DATA_SOURCE_FACTORY_CLASS = "pooled-data-source-factory-class";
private static final String DEFAULT_POOLED_DATA_SOURCE_FACTORY_CLASS =
"org.apache.geode.connectors.jdbc.JdbcPooledDataSourceFactory";
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. Only valid if --pooled."
+ " Defaults to: " + DEFAULT_POOLED_DATA_SOURCE_FACTORY_CLASS;
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 =
"The password that may be required by the external JDBC database when creating a new connection.";
static final String USERNAME = "username";
static final String USERNAME__HELP =
"The username that may be required by the external JDBC database 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 JDBC_DRIVER_CLASS = "jdbc-driver-class";
static final String JDBC_DRIVER_CLASS__HELP =
"This is the fully qualified name of the JDBC driver class.";
static final String 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 POOL_PROPERTIES = "pool-properties";
static final String POOL_PROPERTIES_HELP =
"Used to configure pool properties of a pooled data source. Only valid if --pooled is specified."
+ "The value is a comma separated list of json strings. Each json string contains a name and value. "
+ "If the name starts with \"pool.\", then it will be used to configure the pool data source. "
+ "Otherwise the name value pair will be used to configure the database data source. "
+ "For example 'pool.name1' configures the pool and 'name2' configures the database in the following: "
+ "--pool-properties={'name':'pool.name1','value':'value1'},{'name':'name2','value':'value2'}";
@CliCommand(value = CREATE_DATA_SOURCE, help = CREATE_DATA_SOURCE__HELP)
@CliMetaData(relatedTopic = CliStrings.DEFAULT_TOPIC_GEODE,
interceptor = "org.apache.geode.connectors.jdbc.internal.cli.CreateDataSourceInterceptor")
@ResourceOperation(resource = ResourcePermission.Resource.CLUSTER,
operation = ResourcePermission.Operation.MANAGE)
public ResultModel createDataSource(
@CliOption(key = POOLED_DATA_SOURCE_FACTORY_CLASS,
help = POOLED_DATA_SOURCE_FACTORY_CLASS__HELP) String pooledDataSourceFactoryClass,
@CliOption(key = URL, mandatory = true,
help = URL__HELP) String url,
@CliOption(key = NAME, mandatory = true, help = NAME__HELP) String name,
@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 = JDBC_DRIVER_CLASS,
help = JDBC_DRIVER_CLASS__HELP) String jdbcDriver,
@CliOption(key = POOL_PROPERTIES, optionContext = "splittingRegex=,(?![^{]*\\})",
help = POOL_PROPERTIES_HELP) PoolProperty[] poolProperties) {
JndiBindingsType.JndiBinding configuration = new JndiBindingsType.JndiBinding();
if (pooled) {
if (pooledDataSourceFactoryClass == null) {
pooledDataSourceFactoryClass = DEFAULT_POOLED_DATA_SOURCE_FACTORY_CLASS;
}
configuration.setConnPooledDatasourceClass(pooledDataSourceFactoryClass);
}
configuration.setConnectionUrl(url);
configuration.setJndiName(name);
configuration.setPassword(password);
if (pooled) {
configuration.setType(DATASOURCE_TYPE.POOLED.getType());
} else {
configuration.setType(DATASOURCE_TYPE.SIMPLE.getType());
}
configuration.setUserName(username);
if (jdbcDriver != null) {
configuration.setJdbcDriverClass(jdbcDriver);
}
if (poolProperties != null && poolProperties.length > 0) {
List<ConfigProperty> configProperties = configuration.getConfigProperties();
for (PoolProperty dataSourceProperty : poolProperties) {
String propName = dataSourceProperty.getName();
String propValue = dataSourceProperty.getValue();
configProperties.add(new ConfigProperty(propName, "type", propValue));
}
}
InternalConfigurationPersistenceService service = getConfigurationPersistenceService();
if (service != null) {
CacheConfig cacheConfig = service.getCacheConfig("cluster");
if (cacheConfig != null && exists(cacheConfig.getJndiBindings(), name)) {
String message =
CliStrings.format("Jndi binding with jndi-name \"{0}\" already exists.", name);
return ifNotExists ? ResultModel.createInfo("Skipping: " + message)
: ResultModel.createError(message);
}
}
Set<DistributedMember> targetMembers = findMembers(null, null);
if (targetMembers.size() > 0) {
Object[] arguments = new Object[] {configuration, true};
List<CliFunctionResult> jndiCreationResult = executeAndGetFunctionResult(
new CreateJndiBindingFunction(), arguments, targetMembers);
ResultModel result =
ResultModel.createMemberStatusResult(jndiCreationResult, EXPERIMENTAL, null, false, true);
result.setConfigObject(configuration);
return result;
} else {
if (service != null) {
ResultModel result =
ResultModel.createInfo("No members found, data source saved to cluster configuration.");
result.setConfigObject(configuration);
return result;
} else {
return ResultModel.createError("No members found and cluster configuration unavailable.");
}
}
}
@Override
public boolean updateConfigForGroup(String group, CacheConfig config, Object element) {
config.getJndiBindings().add((JndiBindingsType.JndiBinding) element);
return true;
}
@CliAvailabilityIndicator({CREATE_DATA_SOURCE})
public boolean commandAvailable() {
return isOnlineCommandAvailable();
}
public static class PoolProperty {
private String name;
private String value;
public PoolProperty() {}
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;
}
@Override
public String toString() {
return "PoolProperty [name=" + name + ", value=" + value + "]";
}
}
}