Skip to content

Commit ad20ebe

Browse files
authored
Merge branch 'main' into feat/java-compose-delete-sources
2 parents 7c23e36 + fa81a5e commit ad20ebe

13 files changed

Lines changed: 820 additions & 31 deletions

File tree

java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcProxyUtility.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.google.api.client.http.HttpTransport;
2222
import com.google.api.client.http.apache.v5.Apache5HttpTransport;
23+
import com.google.api.client.http.javanet.NetHttpTransport;
2324
import com.google.api.gax.rpc.TransportChannelProvider;
2425
import com.google.auth.http.HttpTransportFactory;
2526
import com.google.cloud.bigquery.exception.BigQueryJdbcRuntimeException;
@@ -58,6 +59,7 @@ final class BigQueryJdbcProxyUtility {
5859
new BigQueryJdbcCustomLogger(BigQueryJdbcProxyUtility.class.getName());
5960
static final String validPortRegex =
6061
"^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$";
62+
private static final HttpTransport DEFAULT_TRANSPORT = new NetHttpTransport.Builder().build();
6163

6264
private BigQueryJdbcProxyUtility() {}
6365

@@ -136,17 +138,14 @@ static HttpTransportOptions getHttpTransportOptions(
136138
boolean hasProxyOrSsl =
137139
proxyProperties.containsKey(BigQueryJdbcUrlUtility.PROXY_HOST_PROPERTY_NAME)
138140
|| sslTrustStorePath != null;
139-
boolean hasTimeoutConfig = connectTimeout != null || readTimeout != null;
140-
141-
if (!hasProxyOrSsl && !hasTimeoutConfig) {
142-
return null;
143-
}
144141

145142
HttpTransportOptions.Builder httpTransportOptionsBuilder = HttpTransportOptions.newBuilder();
146143
if (hasProxyOrSsl) {
147144
httpTransportOptionsBuilder.setHttpTransportFactory(
148145
getHttpTransportFactory(
149146
proxyProperties, sslTrustStorePath, sslTrustStorePassword, callerClassName));
147+
} else {
148+
httpTransportOptionsBuilder.setHttpTransportFactory(() -> DEFAULT_TRANSPORT);
150149
}
151150

152151
if (connectTimeout != null) {
@@ -178,9 +177,8 @@ private static HttpTransportFactory getHttpTransportFactory(
178177
HttpRoutePlanner httpRoutePlanner = new DefaultProxyRoutePlanner(proxyHostDetails);
179178
httpClientBuilder.setRoutePlanner(httpRoutePlanner);
180179
addAuthToProxyIfPresent(proxyProperties, httpClientBuilder, callerClassName);
181-
} else {
182-
httpClientBuilder.useSystemProperties();
183180
}
181+
httpClientBuilder.useSystemProperties();
184182

185183
if (sslTrustStorePath != null) {
186184
try (FileInputStream trustStoreStream = new FileInputStream(sslTrustStorePath)) {

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcProxyUtilityTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public void testGetHttpTransportOptionsWithNonAuthenticatedProxy() {
161161
}
162162

163163
@Test
164-
public void testGetHttpTransportOptionsWithNoProxySettingsReturnsNull() {
164+
public void testGetHttpTransportOptionsWithNoProxySettingsReturnsDefaultOptions() {
165165
String connection_uri =
166166
"jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
167167
+ "ProjectId=TestProject"
@@ -172,7 +172,8 @@ public void testGetHttpTransportOptionsWithNoProxySettingsReturnsNull() {
172172
HttpTransportOptions result =
173173
BigQueryJdbcProxyUtility.getHttpTransportOptions(
174174
proxyProperties, null, null, null, null, "TestClass");
175-
assertNull(result);
175+
assertNotNull(result);
176+
assertNotNull(result.getHttpTransportFactory());
176177
}
177178

178179
private String getTestResourcePath(String resourceName) throws URISyntaxException {
@@ -299,11 +300,12 @@ public void testGetTransportChannelProvider_noProxyNoSsl_returnsNull() {
299300
}
300301

301302
@Test
302-
public void testGetHttpTransportOptions_noProxyNoSsl_returnsNull() {
303+
public void testGetHttpTransportOptions_noProxyNoSsl_returnsDefaultOptions() {
303304
HttpTransportOptions options =
304305
BigQueryJdbcProxyUtility.getHttpTransportOptions(
305306
Collections.<String, String>emptyMap(), null, null, null, null, "TestClass");
306-
assertNull(options);
307+
assertNotNull(options);
308+
assertNotNull(options.getHttpTransportFactory());
307309
}
308310

309311
@Test
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.bigquery.jdbc.it;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertFalse;
21+
import static org.junit.jupiter.api.Assertions.assertTrue;
22+
23+
import com.google.cloud.bigquery.jdbc.utils.URIBuilder;
24+
import com.google.common.io.CharStreams;
25+
import com.sun.net.httpserver.HttpsConfigurator;
26+
import com.sun.net.httpserver.HttpsParameters;
27+
import com.sun.net.httpserver.HttpsServer;
28+
import java.io.File;
29+
import java.io.InputStream;
30+
import java.io.InputStreamReader;
31+
import java.io.OutputStream;
32+
import java.net.InetSocketAddress;
33+
import java.net.URL;
34+
import java.nio.charset.StandardCharsets;
35+
import java.security.KeyStore;
36+
import java.sql.Connection;
37+
import java.sql.DriverManager;
38+
import java.sql.Statement;
39+
import java.util.ArrayList;
40+
import java.util.List;
41+
import java.util.concurrent.TimeUnit;
42+
import java.util.concurrent.TimeoutException;
43+
import javax.net.ssl.KeyManagerFactory;
44+
import javax.net.ssl.SSLContext;
45+
import javax.net.ssl.SSLParameters;
46+
import org.junit.jupiter.api.AfterAll;
47+
import org.junit.jupiter.api.BeforeAll;
48+
import org.junit.jupiter.api.Test;
49+
50+
public class ITLocalSslValidationTest {
51+
private static final String HOST = "localhost";
52+
private static final String PASSWORD = "changeit";
53+
private static final String KEYSTORE_RESOURCE = "/localhost-keystore.jks";
54+
private static final String TRUSTSTORE_RESOURCE = "/localhost-truststore.jks";
55+
private static final String SUCCESS_MARKER = "SUBPROCESS_RESULT: SUCCESS";
56+
private static final String FAILURE_MARKER_PREFIX = "SUBPROCESS_RESULT: FAILURE - ";
57+
private static final String PKIX_ERROR_MSG = "PKIX path building failed";
58+
59+
private static MockHttpsServer mockServer;
60+
private static int port;
61+
62+
public static class MockHttpsServer {
63+
private final HttpsServer server;
64+
65+
public MockHttpsServer(int port) throws Exception {
66+
server = HttpsServer.create(new InetSocketAddress(HOST, port), 0);
67+
SSLContext sslContext = SSLContext.getInstance("TLS");
68+
69+
KeyStore ks = KeyStore.getInstance("JKS");
70+
try (InputStream stream = getClass().getResourceAsStream(KEYSTORE_RESOURCE)) {
71+
if (stream == null) {
72+
throw new IllegalStateException(
73+
"Keystore resource " + KEYSTORE_RESOURCE + " not found on classpath!");
74+
}
75+
ks.load(stream, PASSWORD.toCharArray());
76+
}
77+
78+
KeyManagerFactory kmf =
79+
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
80+
kmf.init(ks, PASSWORD.toCharArray());
81+
82+
sslContext.init(kmf.getKeyManagers(), null, null);
83+
84+
server.setHttpsConfigurator(
85+
new HttpsConfigurator(sslContext) {
86+
@Override
87+
public void configure(HttpsParameters params) {
88+
try {
89+
SSLContext context = getSSLContext();
90+
SSLParameters sslParams = context.getDefaultSSLParameters();
91+
params.setSSLParameters(sslParams);
92+
} catch (Exception ex) {
93+
ex.printStackTrace();
94+
}
95+
}
96+
});
97+
98+
server.createContext(
99+
"/",
100+
exchange -> {
101+
String path = exchange.getRequestURI().getPath();
102+
String response;
103+
if (path.contains("/queries")) {
104+
response =
105+
"{\n"
106+
+ " \"kind\": \"bigquery#queryResponse\",\n"
107+
+ " \"jobComplete\": true,\n"
108+
+ " \"rows\": [],\n"
109+
+ " \"totalRows\": \"0\",\n"
110+
+ " \"schema\": {\n"
111+
+ " \"fields\": []\n"
112+
+ " }\n"
113+
+ "}";
114+
} else {
115+
response =
116+
"{\n"
117+
+ " \"kind\": \"bigquery#job\",\n"
118+
+ " \"status\": {\n"
119+
+ " \"state\": \"DONE\"\n"
120+
+ " },\n"
121+
+ " \"jobReference\": {\n"
122+
+ " \"projectId\": \"dummy\",\n"
123+
+ " \"jobId\": \"dummy-job\"\n"
124+
+ " },\n"
125+
+ " \"configuration\": {\n"
126+
+ " \"query\": {\n"
127+
+ " \"query\": \"SELECT 1\"\n"
128+
+ " }\n"
129+
+ " },\n"
130+
+ " \"statistics\": {\n"
131+
+ " \"query\": {\n"
132+
+ " \"statementType\": \"SELECT\"\n"
133+
+ " }\n"
134+
+ " }\n"
135+
+ "}";
136+
}
137+
byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8);
138+
exchange.getResponseHeaders().set("Content-Type", "application/json");
139+
exchange.sendResponseHeaders(200, responseBytes.length);
140+
try (OutputStream os = exchange.getResponseBody()) {
141+
os.write(responseBytes);
142+
}
143+
});
144+
}
145+
146+
public void start() {
147+
server.start();
148+
}
149+
150+
public void stop() {
151+
server.stop(0);
152+
}
153+
154+
public int getPort() {
155+
return server.getAddress().getPort();
156+
}
157+
}
158+
159+
private static class ProcessResult {
160+
final int exitCode;
161+
final String stdout;
162+
163+
ProcessResult(int exitCode, String stdout) {
164+
this.exitCode = exitCode;
165+
this.stdout = stdout;
166+
}
167+
}
168+
169+
@BeforeAll
170+
public static void setUp() throws Exception {
171+
mockServer = new MockHttpsServer(0);
172+
mockServer.start();
173+
port = mockServer.getPort();
174+
}
175+
176+
@AfterAll
177+
public static void tearDown() {
178+
if (mockServer == null) {
179+
return;
180+
}
181+
mockServer.stop();
182+
}
183+
184+
private ProcessResult runSubprocess(String trustStore, String password) throws Exception {
185+
String javaHome = System.getProperty("java.home");
186+
String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
187+
String classpath = System.getProperty("java.class.path");
188+
String className = ITLocalSslValidationTest.class.getCanonicalName();
189+
190+
List<String> command = new ArrayList<>();
191+
command.add(javaBin);
192+
if (trustStore != null) {
193+
command.add("-Djavax.net.ssl.trustStore=" + trustStore);
194+
}
195+
if (password != null) {
196+
command.add("-Djavax.net.ssl.trustStorePassword=" + password);
197+
}
198+
command.add("-cp");
199+
command.add(classpath);
200+
command.add(className);
201+
command.add(String.valueOf(port));
202+
203+
ProcessBuilder builder = new ProcessBuilder(command);
204+
builder.redirectErrorStream(true);
205+
Process process = builder.start();
206+
207+
String output = "";
208+
boolean finished = false;
209+
try {
210+
try (InputStreamReader reader =
211+
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)) {
212+
output = CharStreams.toString(reader);
213+
}
214+
finished = process.waitFor(10, TimeUnit.SECONDS);
215+
if (!finished) {
216+
throw new TimeoutException("Subprocess timed out after 10 seconds");
217+
}
218+
int exitCode = process.exitValue();
219+
return new ProcessResult(exitCode, output);
220+
} finally {
221+
if (!finished && process.isAlive()) {
222+
process.destroyForcibly();
223+
}
224+
}
225+
}
226+
227+
@Test
228+
public void testDefaultSslFailsForSelfSigned() throws Exception {
229+
ProcessResult result = runSubprocess(null, null);
230+
assertEquals(1, result.exitCode, "Subprocess should fail. Output:\n" + result.stdout);
231+
assertTrue(result.stdout.contains(PKIX_ERROR_MSG));
232+
}
233+
234+
@Test
235+
public void testCustomTrustStoreSucceeds() throws Exception {
236+
URL trustStoreUrl = getClass().getResource(TRUSTSTORE_RESOURCE);
237+
if (trustStoreUrl == null) {
238+
throw new IllegalStateException(
239+
"Truststore resource " + TRUSTSTORE_RESOURCE + " not found on classpath!");
240+
}
241+
String trustStorePath = new File(trustStoreUrl.toURI()).getAbsolutePath();
242+
ProcessResult result = runSubprocess(trustStorePath, PASSWORD);
243+
244+
assertEquals(0, result.exitCode, "Subprocess failed. Output:\n" + result.stdout);
245+
assertTrue(result.stdout.contains(SUCCESS_MARKER));
246+
assertFalse(
247+
result.stdout.contains(PKIX_ERROR_MSG),
248+
"Handshake failed with SSL error: " + result.stdout);
249+
}
250+
251+
public static void main(String[] args) {
252+
int port = Integer.parseInt(args[0]);
253+
String baseUri = "jdbc:bigquery://https://" + HOST + ":" + port + ";";
254+
String url =
255+
new URIBuilder(baseUri)
256+
.append("EndpointOverrides", "BIGQUERY=https://" + HOST + ":" + port)
257+
.append("ProjectId", "dummy")
258+
.append("OAuthType", 2)
259+
.append("OAuthAccessToken", "dummy-token")
260+
.toString();
261+
try (Connection connection = DriverManager.getConnection(url);
262+
Statement statement = connection.createStatement()) {
263+
statement.execute("SELECT 1");
264+
System.out.println(SUCCESS_MARKER);
265+
System.exit(0);
266+
} catch (Throwable e) {
267+
System.out.println(FAILURE_MARKER_PREFIX + e.getMessage());
268+
e.printStackTrace();
269+
System.exit(1);
270+
}
271+
}
272+
}

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITNightlyBigQueryTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,11 @@ public void testQueryInterruptGracefullyStopsExplicitJob()
232232
});
233233
t.start();
234234
// Allow thread to actually initiate the query
235-
Thread.sleep(3000);
235+
// Even when job is created, we might be using `query` API which means if we cancle within first
236+
// 10 seconds,
237+
// it is similar to Optional job cancellation. Need to wait until after we're in "Wait for job
238+
// completion" mode.
239+
Thread.sleep(15000);
236240
bigQueryStatement.cancel();
237241
// Wait until background thread is finished
238242
t.join();

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITStatementTest.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,10 @@ public void testSetTimeout() throws SQLException {
331331
assertEquals(0, statement.getQueryTimeout());
332332
statement.setQueryTimeout(1);
333333
assertEquals(1, statement.getQueryTimeout());
334-
try {
335-
statement.executeQuery(selectQuery);
336-
} catch (SQLException e) {
337-
assertTrue(true);
338-
assertEquals("SQL execution canceled", e.getMessage());
339-
}
334+
SQLException e = assertThrows(SQLException.class, () -> statement.executeQuery(selectQuery));
335+
assertEquals(
336+
"BigQueryException during runQuery\nJob execution was cancelled: Job timed out",
337+
e.getMessage());
340338
statement.close();
341339
connection.close();
342340
}

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/suites/ITPresubmitTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.cloud.bigquery.jdbc.it.ITConnectionTest;
2424
import com.google.cloud.bigquery.jdbc.it.ITDatabaseMetadataTest;
2525
import com.google.cloud.bigquery.jdbc.it.ITDriverTest;
26+
import com.google.cloud.bigquery.jdbc.it.ITLocalSslValidationTest;
2627
import com.google.cloud.bigquery.jdbc.it.ITResultSetMetadataTest;
2728
import com.google.cloud.bigquery.jdbc.it.ITStatementTest;
2829
import org.junit.platform.suite.api.SelectClasses;
@@ -37,6 +38,7 @@
3738
ITConnectionPoolingTest.class,
3839
ITDatabaseMetadataTest.class,
3940
ITDriverTest.class,
41+
ITLocalSslValidationTest.class,
4042
ITResultSetMetadataTest.class,
4143
ITStatementTest.class
4244
})
2.16 KB
Binary file not shown.
927 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)