PageRenderTime 42ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/treasuredata/jdbc/Config.java

https://gitlab.com/vectorci/td-jdbc
Java | 402 lines | 290 code | 39 blank | 73 comment | 80 complexity | 0ba6f5ba29c71e948f2a0d4217f45265 MD5 | raw file
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. package com.treasuredata.jdbc;
  20. import com.treasure_data.model.Job;
  21. import java.sql.SQLException;
  22. import java.util.Properties;
  23. /**
  24. * JDBC connection configuration
  25. */
  26. public class Config
  27. implements Constants
  28. {
  29. public static final String TD_JDBC_USESSL = "usessl";
  30. public static final String TD_JDBC_USER = "user";
  31. public static final String TD_JDBC_PASSWORD = "password";
  32. public static final String TD_JDBC_APIKEY = "apikey";
  33. public static final String TD_JDBC_JOB_TYPE = "type";
  34. public static final String TD_JDBC_PROXY_HOST = "httpproxyhost";
  35. public static final String TD_JDBC_PROXY_PORT = "httpproxyport";
  36. public static final String TD_JDBC_PROXY_USER = "httpproxyuser";
  37. public static final String TD_JDBC_PROXY_PASSWORD = "httpproxypassword";
  38. // TD JDBC Configuration
  39. public final String url;
  40. public final String database;
  41. public final String user;
  42. public final String password;
  43. public final Job.Type type;
  44. public final ApiConfig apiConfig;
  45. public final int resultRetryCountThreshold;
  46. public final long resultRetryWaitTimeMs;
  47. public Config(
  48. String url,
  49. String database,
  50. String user,
  51. String password,
  52. Job.Type type,
  53. ApiConfig apiConfig,
  54. int resultRetryCountThreshold,
  55. long resultRetryWaitTimeMs
  56. )
  57. throws SQLException
  58. {
  59. this.url = url;
  60. this.database = database;
  61. this.user = user;
  62. this.password = password;
  63. if (type == null || !(type.equals(Job.Type.HIVE) || type.equals(Job.Type.PRESTO))) {
  64. throw new SQLException("invalid job type within URL: " + type);
  65. }
  66. this.type = type;
  67. this.apiConfig = apiConfig;
  68. this.resultRetryCountThreshold = resultRetryCountThreshold;
  69. this.resultRetryWaitTimeMs = resultRetryWaitTimeMs;
  70. }
  71. public Properties toProperties() {
  72. Properties prop = new Properties();
  73. if(user != null) {
  74. prop.setProperty(TD_JDBC_USER, user);
  75. }
  76. if(password != null) {
  77. prop.setProperty(TD_JDBC_PASSWORD, password);
  78. }
  79. prop.setProperty(TD_JDBC_JOB_TYPE, type.type());
  80. prop.putAll(apiConfig.toProperties());
  81. return prop;
  82. }
  83. public static void validateJDBCUrl(String url)
  84. throws SQLException
  85. {
  86. if (url == null || url.isEmpty() || !url.startsWith(URL_PREFIX)) {
  87. throw new SQLException("Invalid JDBC URL: " + url + ". URL prefix must be jdbc:td://");
  88. }
  89. }
  90. private static String getJDBCProperty(Properties prop, String key) {
  91. return System.getProperty(key, prop.getProperty(key));
  92. }
  93. private static String getJDBCProperty(Properties prop, String key, String anotherKey) {
  94. return System.getProperty(key, System.getProperty(anotherKey, prop.getProperty(key, prop.getProperty(anotherKey))));
  95. }
  96. /**
  97. * Apply current configuration to System properties if necessary
  98. */
  99. public void apply() {
  100. if(apiConfig.proxy.isDefined()) {
  101. apiConfig.proxy.get().apply();
  102. }
  103. }
  104. /**
  105. * Create a new connection configuration from a given JDBC URL and custom Properties.
  106. * The precedence of properties is:
  107. *
  108. * <p>
  109. * <li> 1. System properties </li>
  110. * <li> 2. Properties object </li>
  111. * <li> 3. URL parameters </li>
  112. * </p>
  113. * @param jdbcUrl jdbc url
  114. * @param props jdbc properties
  115. * @return connection configuration
  116. * @throws SQLException
  117. */
  118. public static Config newConfig(String jdbcUrl, Properties props)
  119. throws SQLException {
  120. return parseJdbcURL(jdbcUrl).setProperties(props);
  121. }
  122. /**
  123. * jdbc:td://api.treasure-data.com:80/testdb;k1=v1;k2=v2
  124. * +-------+ +-------------------+ ++ +----+ +---------+
  125. * type host port path parameters
  126. * +----------------------+
  127. * endpoint
  128. *
  129. */
  130. public static Config parseJdbcURL(String jdbcUrl)
  131. throws SQLException
  132. {
  133. validateJDBCUrl(jdbcUrl);
  134. ConfigBuilder config = new ConfigBuilder();
  135. boolean hasProxyConfig = false;
  136. ProxyConfig.ProxyConfigBuilder proxyConfig = new ProxyConfig.ProxyConfigBuilder();
  137. ApiConfig.ApiConfigBuilder apiConfig = new ApiConfig.ApiConfigBuilder();
  138. config.setUrl(jdbcUrl);
  139. int postUrlPos = jdbcUrl.length();
  140. // postUrlPos is the END position in url String,
  141. // wrt what remains to be processed.
  142. // i.e., if postUrlPos is 100, url no longer needs to examined at
  143. // index 100 or later.
  144. int semiPos = jdbcUrl.indexOf(';', URL_PREFIX.length());
  145. if (semiPos < 0) {
  146. semiPos = postUrlPos;
  147. }
  148. else {
  149. String params = jdbcUrl.substring(semiPos + 1, postUrlPos);
  150. String[] kvs = params.split(";");
  151. for (String param : kvs) {
  152. String[] kv = param.split("=");
  153. if (kv == null || kv.length != 2) {
  154. throw new SQLException("invalid parameters within URL: " + jdbcUrl);
  155. }
  156. String k = kv[0].toLowerCase();
  157. String v = kv[1];
  158. if (k.equals(TD_JDBC_USER)) {
  159. config.setUser(v);
  160. }
  161. else if (k.equals(TD_JDBC_PASSWORD)) {
  162. config.setPassword(v);
  163. }
  164. else if (k.equals(TD_JDBC_JOB_TYPE)) {
  165. config.setType(Job.toType(v));
  166. }
  167. else if (k.equals(TD_JDBC_USESSL)) {
  168. apiConfig.setUseSSL(Boolean.parseBoolean(kv[1].toLowerCase()));
  169. }
  170. else if (k.equals(TD_JDBC_PROXY_HOST)) {
  171. hasProxyConfig = true;
  172. proxyConfig.setHost(v);
  173. }
  174. else if (k.equals(TD_JDBC_PROXY_PORT)) {
  175. hasProxyConfig = true;
  176. proxyConfig.setPort(Integer.parseInt(v));
  177. }
  178. else if (k.equals(TD_JDBC_PROXY_USER)) {
  179. hasProxyConfig = true;
  180. proxyConfig.setUser(v);
  181. }
  182. else if (k.equals(TD_JDBC_PROXY_PASSWORD)) {
  183. hasProxyConfig = true;
  184. proxyConfig.setPassword(v);
  185. }
  186. }
  187. }
  188. int slashPos = jdbcUrl.indexOf('/', URL_PREFIX.length());
  189. if(slashPos == -1) {
  190. slashPos = jdbcUrl.indexOf(';', URL_PREFIX.length());
  191. }
  192. // Set the API endpoint specified in JDBC URL
  193. String endpoint = null;
  194. try {
  195. endpoint = jdbcUrl.substring(URL_PREFIX.length(), slashPos);
  196. }
  197. catch (IndexOutOfBoundsException t) {
  198. throw new SQLException("invalid endpoint within URL: " + jdbcUrl);
  199. }
  200. if (endpoint != null && !endpoint.isEmpty()) {
  201. int i = endpoint.indexOf(':');
  202. if (i >= 0) {
  203. if (i != 0) {
  204. apiConfig.setEndpoint(endpoint.substring(0, i));
  205. }
  206. String portStr = endpoint.substring(i + 1, endpoint.length());
  207. try {
  208. apiConfig.setPort(Integer.parseInt(portStr));
  209. }
  210. catch (NumberFormatException t) {
  211. throw new SQLException(String.format("invalid port '%s' within URL: %s", portStr, jdbcUrl));
  212. }
  213. }
  214. else { // i < 0
  215. apiConfig.setEndpoint(endpoint);
  216. }
  217. }
  218. // Set database name
  219. if (slashPos >= 0 && jdbcUrl.charAt(slashPos) == '/') {
  220. try {
  221. String rawDatabase = jdbcUrl.substring(slashPos + 1, semiPos);
  222. if (rawDatabase != null && !rawDatabase.isEmpty()) {
  223. config.setDatabase(rawDatabase);
  224. }
  225. }
  226. catch (IndexOutOfBoundsException t) {
  227. throw new SQLException("invalid database name within URL: " + jdbcUrl);
  228. }
  229. }
  230. if (hasProxyConfig) {
  231. apiConfig.setProxyConfig(proxyConfig.createProxyConfig());
  232. }
  233. config.setApiConfig(apiConfig.createApiConfig());
  234. return config.createConnectionConfig();
  235. }
  236. private static boolean isEmptyString(String s) {
  237. return s == null || (s != null && s.isEmpty());
  238. }
  239. /**
  240. * Overwrite the configuration with given Properties then System properties.
  241. *
  242. * @param props
  243. * @return
  244. * @throws SQLException
  245. */
  246. public Config setProperties(Properties props) throws SQLException {
  247. ConfigBuilder config = new ConfigBuilder(this);
  248. ApiConfig.ApiConfigBuilder apiConfig = new ApiConfig.ApiConfigBuilder(this.apiConfig);
  249. ProxyConfig.ProxyConfigBuilder proxyConfig;
  250. boolean hasProxyConfig = false;
  251. if(apiConfig.proxy.isDefined()) {
  252. proxyConfig = new ProxyConfig.ProxyConfigBuilder(apiConfig.proxy.get());
  253. hasProxyConfig = true;
  254. }
  255. else {
  256. proxyConfig = new ProxyConfig.ProxyConfigBuilder();
  257. }
  258. // Override URL parameters via Properties, then System properties.
  259. // api endpoint
  260. String apiHost = getJDBCProperty(props, TD_API_SERVER_HOST);
  261. if (!isEmptyString(apiHost)) {
  262. apiConfig.setEndpoint(apiHost);
  263. }
  264. // api port
  265. String apiPortStr = getJDBCProperty(props, TD_API_SERVER_PORT);
  266. if (!isEmptyString(apiPortStr)) {
  267. try {
  268. apiConfig.setPort(Integer.parseInt(apiPortStr));
  269. }
  270. catch (NumberFormatException e) {
  271. throw new SQLException("port number is invalid: " + apiPortStr);
  272. }
  273. }
  274. // API scheme (HTTP or HTTPS)
  275. String useSSL = getJDBCProperty(props, TD_JDBC_USESSL);
  276. if (!isEmptyString(useSSL)) {
  277. apiConfig.setUseSSL(Boolean.parseBoolean(useSSL));
  278. }
  279. // TD API key
  280. String apiKey = null;
  281. // Check environment variable
  282. if(System.getenv().containsKey("TD_API_KEY")) {
  283. apiKey = System.getenv().get("TD_API_KEY");
  284. }
  285. if(apiKey == null) {
  286. apiKey = getJDBCProperty(props, TD_JDBC_APIKEY, TD_API_KEY);
  287. }
  288. if(isEmptyString(apiKey)) {
  289. apiKey = null;
  290. }
  291. else {
  292. apiConfig.setApiKey(apiKey);
  293. }
  294. // user
  295. String user = getJDBCProperty(props, TD_JDBC_USER);
  296. if (apiKey == null && isEmptyString(user)) {
  297. throw new SQLException("User is not specified. Use Properties object to set 'user'");
  298. }
  299. config.setUser(user);
  300. // password
  301. String password = getJDBCProperty(props, TD_JDBC_PASSWORD);
  302. if (apiKey == null && isEmptyString(password)) {
  303. throw new SQLException("Password is not specified. Use Properties object to set 'password'");
  304. }
  305. config.setPassword(password);
  306. // If both user and password are specified, use this pair instead of TD_API_KEY
  307. if(!isEmptyString(user) && !isEmptyString(password)) {
  308. apiConfig.unsetApiKey();
  309. }
  310. // retry settings
  311. String retryCountThreshold = getJDBCProperty(props, TD_JDBC_RESULT_RETRYCOUNT_THRESHOLD);
  312. if (!isEmptyString(retryCountThreshold)) {
  313. try {
  314. config.setResultRetryCountThreshold(Integer.parseInt(retryCountThreshold));
  315. }
  316. catch (NumberFormatException e) {
  317. throw new SQLException("Invalid value for td.jdbc.result.retrycount.threshold: " + retryCountThreshold);
  318. }
  319. }
  320. String retryWaitTimeMs = getJDBCProperty(props, TD_JDBC_RESULT_RETRY_WAITTIME);
  321. if (!isEmptyString(retryWaitTimeMs)) {
  322. try {
  323. config.setResultRetryWaitTimeMs(Long.parseLong(retryWaitTimeMs));
  324. }
  325. catch (NumberFormatException e) {
  326. throw new SQLException("Invalid value for td.jdbc.result.retry.waittime: " + retryWaitTimeMs);
  327. }
  328. }
  329. // proxy settings: host and port are supported by Java runtime.
  330. // http.proxyUser and http.proxyPassword are no longer supported but
  331. // we set Authenticator by ourselves.
  332. String httpProxyHost = getJDBCProperty(props, TD_JDBC_PROXY_HOST, "http.proxyHost");
  333. if (!isEmptyString(httpProxyHost)) {
  334. hasProxyConfig = true;
  335. proxyConfig.setHost(httpProxyHost);
  336. }
  337. String httpProxyPort = getJDBCProperty(props, TD_JDBC_PROXY_PORT, "http.proxyPort");
  338. if (!isEmptyString(httpProxyPort)) {
  339. hasProxyConfig = true;
  340. try {
  341. proxyConfig.setPort(Integer.parseInt(httpProxyPort));
  342. }
  343. catch(NumberFormatException e) {
  344. throw new SQLException("Proxy port is not a number: " + httpProxyPort);
  345. }
  346. }
  347. String httpProxyUser = getJDBCProperty(props, TD_JDBC_PROXY_USER, "http.proxyUser");
  348. if (!isEmptyString(httpProxyUser)) {
  349. hasProxyConfig = true;
  350. proxyConfig.setUser(httpProxyUser);
  351. }
  352. String httpProxyPassword = getJDBCProperty(props, TD_JDBC_PROXY_PASSWORD, "http.proxyPassword");
  353. if (!isEmptyString(httpProxyPassword)) {
  354. hasProxyConfig = true;
  355. proxyConfig.setPassword(httpProxyPassword);
  356. }
  357. if (hasProxyConfig) {
  358. apiConfig.setProxyConfig(proxyConfig.createProxyConfig());
  359. }
  360. config.setApiConfig(apiConfig.createApiConfig());
  361. return config.createConnectionConfig();
  362. }
  363. }