diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f28db8d..808b20f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: sed -i 's/^IBMI_PORT=/IBMI_PORT=${{ secrets.IBMI_PORT }}/' ${{ env.CONFIG_PROPERTIES }} - name: Build with Maven - run: mvn -B package --file pom.xml + run: mvn --batch-mode clean package --file pom.xml - name: Update Dependency Graph uses: advanced-security/maven-dependency-submission-action@v4 diff --git a/checkstyle/suppressions.xml b/checkstyle/suppressions.xml index fe7f865..9014a70 100644 --- a/checkstyle/suppressions.xml +++ b/checkstyle/suppressions.xml @@ -11,5 +11,6 @@ + diff --git a/pom.xml b/pom.xml index 1f7d844..8f2e103 100644 --- a/pom.xml +++ b/pom.xml @@ -274,6 +274,9 @@ org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc-plugin.version} + + all,-missing + diff --git a/src/main/java/io/github/mapepire_ibmi/NoAuthTrustManager.java b/src/main/java/io/github/mapepire_ibmi/NoAuthTrustManager.java deleted file mode 100644 index ada7154..0000000 --- a/src/main/java/io/github/mapepire_ibmi/NoAuthTrustManager.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.mapepire_ibmi; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.X509TrustManager; - -/** - * Represents a manager that handles which X509 certificates may be used to - * authenticate the remote side of a secure socket. - */ -class NoAuthTrustManager implements X509TrustManager { - @Override - public void checkClientTrusted(final X509Certificate[] chain, final String authType) - throws CertificateException { - } - - @Override - public void checkServerTrusted(final X509Certificate[] chain, final String authType) - throws CertificateException { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } -} diff --git a/src/main/java/io/github/mapepire_ibmi/Pool.java b/src/main/java/io/github/mapepire_ibmi/Pool.java index cce0bfe..217f46c 100644 --- a/src/main/java/io/github/mapepire_ibmi/Pool.java +++ b/src/main/java/io/github/mapepire_ibmi/Pool.java @@ -1,9 +1,5 @@ package io.github.mapepire_ibmi; -import java.net.URISyntaxException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -11,19 +7,14 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; - import io.github.mapepire_ibmi.types.JobStatus; import io.github.mapepire_ibmi.types.PoolAddOptions; import io.github.mapepire_ibmi.types.PoolOptions; import io.github.mapepire_ibmi.types.QueryOptions; import io.github.mapepire_ibmi.types.QueryResult; import io.github.mapepire_ibmi.types.exceptions.ClientException; -import io.github.mapepire_ibmi.types.exceptions.UnknownServerException; /** * Represents a connection pool for managing SQL jobs. @@ -59,21 +50,8 @@ public Pool(PoolOptions options) { * size. * * @return A CompletableFuture that resolves when all jobs have been created. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException - * @throws ClientException */ - public CompletableFuture init() - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException, - ClientException { + public CompletableFuture init() throws Exception { if (this.options.getMaxSize() <= 0) { throw new ClientException("Max size must be greater than 0"); } else if (this.options.getStartingSize() <= 0) { @@ -126,19 +104,8 @@ public void cleanup() { * Add a new job to the pool. * * @return A CompletableFuture that resolves to the new job. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public CompletableFuture addJob() - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException { + public CompletableFuture addJob() throws Exception { PoolAddOptions poolAddOptions = new PoolAddOptions(); return this.addJob(poolAddOptions); } @@ -148,19 +115,8 @@ public CompletableFuture addJob() * * @param options Options for configuring an addition to the connection pool. * @return A CompletableFuture that resolves to the new job. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public CompletableFuture addJob(PoolAddOptions options) - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException { + public CompletableFuture addJob(PoolAddOptions options) throws Exception { if (options.getExistingJob() != null) { cleanup(); } @@ -201,19 +157,8 @@ public SqlJob getReadyJob() { * full but all jobs are busy. * * @return The retrieved job. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public synchronized SqlJob getJob() - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException { + public synchronized SqlJob getJob() throws Exception { SqlJob job = this.getReadyJob(); if (job == null) { // This code finds a job that is busy, but has the least requests on the queue @@ -241,19 +186,8 @@ public synchronized SqlJob getJob() * otherwise, it may create a new job if the pool is not full. * * @return A CompletableFuture that resolves to a ready job. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public CompletableFuture waitForJob() - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException { + public CompletableFuture waitForJob() throws Exception { return this.waitForJob(false); } @@ -264,19 +198,8 @@ public CompletableFuture waitForJob() * @param useNewJob Whether a new job should be created even if the pool is * full. * @return A CompletableFuture that resolves to a ready job. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public CompletableFuture waitForJob(boolean useNewJob) - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException { + public CompletableFuture waitForJob(boolean useNewJob) throws Exception { SqlJob job = getReadyJob(); if (job == null) { @@ -298,19 +221,8 @@ public CompletableFuture waitForJob(boolean useNewJob) * the pool. * * @return A CompletableFuture that resolves to a ready job or a new job. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public CompletableFuture popJob() - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException { + public CompletableFuture popJob() throws Exception { // TODO: dead code: what to do with it? SqlJob readyJob = getReadyJob(); if (readyJob != null) { @@ -329,19 +241,8 @@ public CompletableFuture popJob() * * @param sql The SQL query. * @return A new Query instance. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public Query query(String sql) - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException { + public Query query(String sql) throws Exception { QueryOptions queryOptions = new QueryOptions(); return this.query(sql, queryOptions); } @@ -352,19 +253,8 @@ public Query query(String sql) * @param sql The SQL query. * @param opts The options for configuring the query. * @return A new Query instance. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public Query query(String sql, QueryOptions opts) - throws JsonMappingException, KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, - InterruptedException, ExecutionException, URISyntaxException, SQLException, UnknownServerException { + public Query query(String sql, QueryOptions opts) throws Exception { SqlJob job = this.getJob(); return job.query(sql, opts); } @@ -375,20 +265,8 @@ public Query query(String sql, QueryOptions opts) * @param The type of data to be returned. * @param sql The SQL command to execute. * @return A CompletableFuture that resolves to the query result. - * @throws ClientException - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException */ - public CompletableFuture> execute(String sql) throws JsonMappingException, - KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, InterruptedException, - ExecutionException, URISyntaxException, SQLException, UnknownServerException, ClientException { + public CompletableFuture> execute(String sql) throws Exception { QueryOptions queryOptions = new QueryOptions(); return this.execute(sql, queryOptions); } @@ -400,20 +278,8 @@ public CompletableFuture> execute(String sql) throws JsonMapp * @param sql The SQL command to execute. * @param opts The options for configuring the query. * @return A CompletableFuture that resolves to the query result. - * @throws UnknownServerException - * @throws SQLException - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws JsonProcessingException - * @throws KeyManagementException - * @throws JsonMappingException - * @throws ClientException */ - public CompletableFuture> execute(String sql, QueryOptions opts) throws JsonMappingException, - KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, InterruptedException, - ExecutionException, URISyntaxException, SQLException, UnknownServerException, ClientException { + public CompletableFuture> execute(String sql, QueryOptions opts) throws Exception { SqlJob job = this.getJob(); return job.execute(sql, opts); } diff --git a/src/main/java/io/github/mapepire_ibmi/Query.java b/src/main/java/io/github/mapepire_ibmi/Query.java index 66d29df..62befae 100644 --- a/src/main/java/io/github/mapepire_ibmi/Query.java +++ b/src/main/java/io/github/mapepire_ibmi/Query.java @@ -5,11 +5,8 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -141,17 +138,14 @@ public static synchronized List getOpenIds(SqlJob forJob) { /** * Clean up queries that are done or are in error state from the global query * list. - * - * @throws ExecutionException - * @throws InterruptedException */ - public synchronized CompletableFuture cleanup() throws InterruptedException, ExecutionException { + public synchronized CompletableFuture cleanup() throws Exception { List> futures = globalQueryList.stream() .filter(query -> query.getState() == QueryState.RUN_DONE || query.getState() == QueryState.ERROR) .map(query -> CompletableFuture.runAsync(() -> { try { query.close(); - } catch (JsonProcessingException | InterruptedException | ExecutionException e) { + } catch (Exception e) { e.printStackTrace(); } })) @@ -170,15 +164,8 @@ public synchronized CompletableFuture cleanup() throws InterruptedExceptio * * @param The type of data to be returned. * @return A CompletableFuture that resolves to the query result. - * @throws SQLException - * @throws ExecutionException - * @throws InterruptedException - * @throws ClientException - * @throws JsonProcessingException - * @throws JsonMappingException */ - public CompletableFuture> execute() throws JsonMappingException, JsonProcessingException, - ClientException, InterruptedException, ExecutionException, SQLException { + public CompletableFuture> execute() throws Exception { return this.execute(100); } @@ -188,15 +175,8 @@ public CompletableFuture> execute() throws JsonMappingExcepti * @param The type of data to be returned. * @param rowsToFetch The number of rows to fetch. * @return A CompletableFuture that resolves to the query result. - * @throws ClientException - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws SQLException */ - public CompletableFuture> execute(int rowsToFetch) throws ClientException, JsonMappingException, - JsonProcessingException, InterruptedException, ExecutionException, SQLException { + public CompletableFuture> execute(int rowsToFetch) throws Exception { if (rowsToFetch <= 0) { throw new ClientException("Rows to fetch must be greater than 0"); } @@ -275,16 +255,8 @@ public CompletableFuture> execute(int rowsToFetch) throws Cli * Fetch more rows from the currently running query. * * @return A CompletableFuture that resolves to the query result. - * @throws SQLException - * @throws ExecutionException - * @throws InterruptedException - * @throws UnknownServerException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws ClientException */ - public CompletableFuture> fetchMore() throws JsonMappingException, JsonProcessingException, - UnknownServerException, InterruptedException, ExecutionException, SQLException, ClientException { + public CompletableFuture> fetchMore() throws Exception { return this.fetchMore(this.rowsToFetch); } @@ -293,17 +265,8 @@ public CompletableFuture> fetchMore() throws JsonMappingExcep * * @param rowsToFetch The number of additional rows to fetch. * @return A CompletableFuture that resolves to the query result. - * @throws ClientException - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws SQLException - * @throws UnknownServerException */ - public CompletableFuture> fetchMore(int rowsToFetch) - throws ClientException, JsonMappingException, - JsonProcessingException, InterruptedException, ExecutionException, SQLException, UnknownServerException { + public CompletableFuture> fetchMore(int rowsToFetch) throws Exception { if (rowsToFetch <= 0) { throw new ClientException("Rows to fetch must be greater than 0"); } @@ -358,13 +321,8 @@ public CompletableFuture> fetchMore(int rowsToFetch) * Close the query. * * @return A CompletableFuture that resolves when the query is closed. - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException */ - public CompletableFuture close() - throws JsonMappingException, JsonProcessingException, InterruptedException, ExecutionException { + public CompletableFuture close() throws Exception { if (correlationId != null && state != QueryState.RUN_DONE) { state = QueryState.RUN_DONE; diff --git a/src/main/java/io/github/mapepire_ibmi/SqlJob.java b/src/main/java/io/github/mapepire_ibmi/SqlJob.java index eaad875..c129ae6 100644 --- a/src/main/java/io/github/mapepire_ibmi/SqlJob.java +++ b/src/main/java/io/github/mapepire_ibmi/SqlJob.java @@ -1,11 +1,14 @@ package io.github.mapepire_ibmi; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.net.URI; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; +import java.security.KeyStore; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.sql.SQLException; import java.util.Arrays; import java.util.Base64; @@ -14,19 +17,19 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft_6455; import org.java_websocket.handshake.ServerHandshake; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -45,7 +48,6 @@ import io.github.mapepire_ibmi.types.SetConfigResult; import io.github.mapepire_ibmi.types.TransactionEndType; import io.github.mapepire_ibmi.types.VersionCheckResult; -import io.github.mapepire_ibmi.types.exceptions.ClientException; import io.github.mapepire_ibmi.types.exceptions.UnknownServerException; import io.github.mapepire_ibmi.types.jdbcOptions.Option; import io.github.mapepire_ibmi.types.jdbcOptions.TransactionIsolation; @@ -147,17 +149,95 @@ public static synchronized String getNewUniqueId(String prefix) { * * @param db2Server The server details for the connection. * @return A CompletableFuture that resolves to the WebSocketClient instance. - * @throws NoSuchAlgorithmException - * @throws KeyManagementException - * @throws URISyntaxException */ - private CompletableFuture getChannel(DaemonServer db2Server) - throws NoSuchAlgorithmException, KeyManagementException, URISyntaxException { - SSLContext sslContext = SSLContext.getInstance("TLS"); - NoAuthTrustManager trustManager = new NoAuthTrustManager(); - sslContext.init(null, new TrustManager[] { trustManager }, new SecureRandom()); - SSLSocketFactory factory = sslContext.getSocketFactory(); + private CompletableFuture getChannel(DaemonServer db2Server) throws Exception { + TrustManagerFactory tmf; + X509TrustManager customTrustManager = null; + X509TrustManager jdkTrustManager = null; + + if (db2Server.getCa() != null) { + // Convert custom CA from string to X509Certificate + InputStream inputStream = new ByteArrayInputStream(db2Server.getCa().getBytes()); + X509Certificate caCert = (X509Certificate) CertificateFactory.getInstance("X509") + .generateCertificate(inputStream); + + // Create a custom key store and load custom certificate + KeyStore customKeyStore = KeyStore.getInstance("PKCS12"); + customKeyStore.load(null, null); + customKeyStore.setCertificateEntry("mapepire-ca", caCert); + + // Initialize a TrustManagerFactory with custom key store + tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(customKeyStore); + + // Get the custom X509TrustManager + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509TrustManager) { + customTrustManager = (X509TrustManager) tm; + break; + } + } + } + + // Initialize TrustManagerFactory + tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + + // Get the JDK X509TrustManager + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509TrustManager) { + jdkTrustManager = (X509TrustManager) tm; + break; + } + } + + final X509TrustManager finalCustomTrustManager = customTrustManager; + final X509TrustManager finalJdkTrustManager = jdkTrustManager; + X509TrustManager mapepireTrustManager = new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + if (finalCustomTrustManager != null) { + X509Certificate[] jdkTrust = finalJdkTrustManager.getAcceptedIssuers(); + X509Certificate[] custTrust = finalCustomTrustManager.getAcceptedIssuers(); + X509Certificate[] merge = new X509Certificate[custTrust.length + jdkTrust.length]; + + for (int i = 0; i < custTrust.length; i++) { + merge[i] = custTrust[i]; + } + + for (int i = 0; i < jdkTrust.length; i++) { + merge[custTrust.length + i] = jdkTrust[i]; + } + + return merge; + } else { + return finalJdkTrustManager.getAcceptedIssuers(); + } + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (db2Server.getRejectUnauthorized()) { + if (finalCustomTrustManager != null) { + try { + finalCustomTrustManager.checkServerTrusted(chain, authType); + } catch (CertificateException e) { + finalJdkTrustManager.checkServerTrusted(chain, authType); + } + } else { + finalJdkTrustManager.checkServerTrusted(chain, authType); + } + } + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + }; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { mapepireTrustManager }, new SecureRandom()); + SSLContext.setDefault(sslContext); URI uri = new URI("wss://" + db2Server.getHost() + ":" + db2Server.getPort() + "/db/"); Map httpHeaders = new HashMap<>(); String auth = db2Server.getUser() + ":" + db2Server.getPassword(); @@ -214,14 +294,9 @@ public void onError(Exception e) { } } }; + SSLSocketFactory factory = sslContext.getSocketFactory(); wsc.setSocketFactory(factory); - // TODO: Implement - // if (db2Server.getIgnoreUnauthorized()) { - // } - // if (db2Server.getCa() != null) { - // } - return CompletableFuture.completedFuture(wsc); } @@ -230,13 +305,8 @@ public void onError(Exception e) { * * @param content The message content to send. * @return A CompletableFuture that resolves to the server's response. - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws ExecutionException - * @throws InterruptedException */ - public CompletableFuture send(String content) - throws JsonMappingException, JsonProcessingException, InterruptedException, ExecutionException { + public CompletableFuture send(String content) throws Exception { if (this.isTracingChannelData) { System.out.println("\n>> " + content); } @@ -280,19 +350,8 @@ public int getRunningCount() { * * @param db2Server The server details for the connection. * @return A CompletableFuture that resolves to the connection result. - * @throws URISyntaxException - * @throws ExecutionException - * @throws InterruptedException - * @throws NoSuchAlgorithmException - * @throws KeyManagementException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws SQLException - * @throws UnknownServerException - */ - public CompletableFuture connect(DaemonServer db2Server) throws KeyManagementException, - NoSuchAlgorithmException, InterruptedException, ExecutionException, URISyntaxException, - JsonMappingException, JsonProcessingException, SQLException, UnknownServerException { + */ + public CompletableFuture connect(DaemonServer db2Server) throws Exception { this.status = JobStatus.Connecting; ObjectMapper objectMapper = SingletonObjectMapper.getInstance(); @@ -390,17 +449,8 @@ public Query query(String sql, QueryOptions opts) { * @param The type of data to be returned. * @param sql The SQL command to execute. * @return A CompletableFuture that resolves to the query result. - * @throws UnknownServerException - * @throws SQLException - * @throws ExecutionException - * @throws InterruptedException - * @throws ClientException - * @throws JsonProcessingException - * @throws JsonMappingException - */ - public CompletableFuture> execute(String sql) - throws JsonMappingException, JsonProcessingException, ClientException, InterruptedException, - ExecutionException, SQLException, UnknownServerException { + */ + public CompletableFuture> execute(String sql) throws Exception { return this.execute(sql, new QueryOptions()); } @@ -411,17 +461,8 @@ public CompletableFuture> execute(String sql) * @param sql The SQL command to execute. * @param opts The options for configuring the query. * @return A CompletableFuture that resolves to the query result. - * @throws SQLException - * @throws ExecutionException - * @throws InterruptedException - * @throws ClientException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws UnknownServerException - */ - public CompletableFuture> execute(String sql, QueryOptions opts) - throws JsonMappingException, JsonProcessingException, ClientException, InterruptedException, - ExecutionException, SQLException, UnknownServerException { + */ + public CompletableFuture> execute(String sql, QueryOptions opts) throws Exception { Query query = query(sql, opts); return query.execute() .thenCompose(queryResult -> { @@ -448,15 +489,8 @@ public CompletableFuture> execute(String sql, QueryOptions op * Get the version information from the database server. * * @return A CompletableFuture that resolves to the version check result. - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws SQLException - * @throws UnknownServerException - */ - public CompletableFuture getVersion() throws JsonMappingException, JsonProcessingException, - InterruptedException, ExecutionException, SQLException, UnknownServerException { + */ + public CompletableFuture getVersion() throws Exception { ObjectMapper objectMapper = SingletonObjectMapper.getInstance(); ObjectNode versionRequest = objectMapper.createObjectNode(); versionRequest.put("id", SqlJob.getNewUniqueId()); @@ -489,15 +523,8 @@ public CompletableFuture getVersion() throws JsonMappingExce * * @param statement The SQL statement to explain. * @return A CompletableFuture that resolves to the explain results. - * @throws SQLException - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws UnknownServerException - */ - public CompletableFuture> explain(String statement) throws JsonMappingException, - JsonProcessingException, InterruptedException, ExecutionException, SQLException, UnknownServerException { + */ + public CompletableFuture> explain(String statement) throws Exception { return this.explain(statement, ExplainType.RUN); } @@ -507,15 +534,8 @@ public CompletableFuture> explain(String statement) throws Jso * @param statement The SQL statement to explain. * @param type The type of explain to perform (default is ExplainType.Run). * @return A CompletableFuture that resolves to the explain results. - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws SQLException - * @throws UnknownServerException - */ - public CompletableFuture> explain(String statement, ExplainType type) throws JsonMappingException, - JsonProcessingException, InterruptedException, ExecutionException, SQLException, UnknownServerException { + */ + public CompletableFuture> explain(String statement, ExplainType type) throws Exception { ObjectMapper objectMapper = SingletonObjectMapper.getInstance(); ObjectNode explainRequest = objectMapper.createObjectNode(); explainRequest.put("id", SqlJob.getNewUniqueId()); @@ -556,15 +576,8 @@ public String getTraceFilePath() { * Get trace data from the backend. * * @return A CompletableFuture that resolves to the trace data result. - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws SQLException - * @throws UnknownServerException - */ - public CompletableFuture getTraceData() throws JsonMappingException, JsonProcessingException, - InterruptedException, ExecutionException, SQLException, UnknownServerException { + */ + public CompletableFuture getTraceData() throws Exception { ObjectMapper objectMapper = SingletonObjectMapper.getInstance(); ObjectNode traceDataRequest = objectMapper.createObjectNode(); traceDataRequest.put("id", SqlJob.getNewUniqueId()); @@ -597,15 +610,8 @@ public CompletableFuture getTraceData() throws JsonMappingEx * * @param dest The server trace destination. * @return A CompletableFuture that resolves to the set config result. - * @throws JsonMappingException - * @throws JsonProcessingException - * @throws InterruptedException - * @throws ExecutionException - * @throws SQLException - * @throws UnknownServerException - */ - public CompletableFuture setTraceDest(ServerTraceDest dest) throws JsonMappingException, - JsonProcessingException, InterruptedException, ExecutionException, SQLException, UnknownServerException { + */ + public CompletableFuture setTraceDest(ServerTraceDest dest) throws Exception { return setTraceConfig(dest, null, null, null); } @@ -614,15 +620,8 @@ public CompletableFuture setTraceDest(ServerTraceDest dest) thr * * @param level The server trace level. * @return A CompletableFuture that resolves to the set config result. - * @throws JsonMappingException - * @throws JsonProcessingException - * @throws InterruptedException - * @throws ExecutionException - * @throws SQLException - * @throws UnknownServerException - */ - public CompletableFuture setTraceLevel(ServerTraceLevel level) throws JsonMappingException, - JsonProcessingException, InterruptedException, ExecutionException, SQLException, UnknownServerException { + */ + public CompletableFuture setTraceLevel(ServerTraceLevel level) throws Exception { return setTraceConfig(null, level, null, null); } @@ -631,16 +630,8 @@ public CompletableFuture setTraceLevel(ServerTraceLevel level) * * @param jtOpenTraceDest The JTOpen trace data destination. * @return A CompletableFuture that resolves to the set config result. - * @throws JsonMappingException - * @throws JsonProcessingException - * @throws InterruptedException - * @throws ExecutionException - * @throws SQLException - * @throws UnknownServerException - */ - public CompletableFuture setJtOpenTraceDest(ServerTraceDest jtOpenTraceDest) - throws JsonMappingException, JsonProcessingException, InterruptedException, ExecutionException, - SQLException, UnknownServerException { + */ + public CompletableFuture setJtOpenTraceDest(ServerTraceDest jtOpenTraceDest) throws Exception { return setTraceConfig(null, null, jtOpenTraceDest, null); } @@ -649,16 +640,8 @@ public CompletableFuture setJtOpenTraceDest(ServerTraceDest jtO * * @param jtOpenTraceLevel The JTOpen trace level. * @return A CompletableFuture that resolves to the set config result. - * @throws JsonMappingException - * @throws JsonProcessingException - * @throws InterruptedException - * @throws ExecutionException - * @throws SQLException - * @throws UnknownServerException - */ - public CompletableFuture setJtOpenTraceLevel(ServerTraceLevel jtOpenTraceLevel) - throws JsonMappingException, JsonProcessingException, InterruptedException, ExecutionException, - SQLException, UnknownServerException { + */ + public CompletableFuture setJtOpenTraceLevel(ServerTraceLevel jtOpenTraceLevel) throws Exception { return setTraceConfig(null, null, null, jtOpenTraceLevel); } @@ -670,17 +653,9 @@ public CompletableFuture setJtOpenTraceLevel(ServerTraceLevel j * @param jtOpenTraceDest The JTOpen trace data destination. * @param jtOpenTraceLevel The JTOpen trace level. * @return A CompletableFuture that resolves to the set config result. - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException - * @throws SQLException - * @throws UnknownServerException */ public CompletableFuture setTraceConfig(ServerTraceDest dest, ServerTraceLevel level, - ServerTraceDest jtOpenTraceDest, ServerTraceLevel jtOpenTraceLevel) - throws JsonMappingException, JsonProcessingException, InterruptedException, ExecutionException, - SQLException, UnknownServerException { + ServerTraceDest jtOpenTraceDest, ServerTraceLevel jtOpenTraceLevel) throws Exception { ObjectMapper objectMapper = SingletonObjectMapper.getInstance(); ObjectNode setTraceConfigRequest = objectMapper.createObjectNode(); setTraceConfigRequest.put("id", SqlJob.getNewUniqueId()); @@ -755,15 +730,8 @@ public boolean underCommitControl() { * * @return A CompletableFuture that resolves to the count of pending * transactions. - * @throws SQLException - * @throws ClientException - * @throws ExecutionException - * @throws InterruptedException - * @throws JsonProcessingException - * @throws JsonMappingException - */ - public CompletableFuture getPendingTransactions() throws JsonMappingException, JsonProcessingException, - InterruptedException, ExecutionException, ClientException, SQLException { + */ + public CompletableFuture getPendingTransactions() throws Exception { ObjectMapper objectMapper = SingletonObjectMapper.getInstance(); String transactionCountQuery = String.join("\n", Arrays.asList( "select count(*) as thecount", @@ -796,16 +764,8 @@ public CompletableFuture getPendingTransactions() throws JsonMappingExc * @param type The type of transaction ending (commit or rollback). * @return A CompletableFuture that resolves to the result of the transaction * operation. - * @throws SQLException - * @throws ExecutionException - * @throws InterruptedException - * @throws ClientException - * @throws JsonProcessingException - * @throws JsonMappingException - */ - public CompletableFuture> endTransaction(TransactionEndType type) - throws JsonMappingException, JsonProcessingException, ClientException, InterruptedException, - ExecutionException, SQLException { + */ + public CompletableFuture> endTransaction(TransactionEndType type) throws Exception { String query; switch (type) { diff --git a/src/main/java/io/github/mapepire_ibmi/Tls.java b/src/main/java/io/github/mapepire_ibmi/Tls.java index 1ca636f..9aa0174 100644 --- a/src/main/java/io/github/mapepire_ibmi/Tls.java +++ b/src/main/java/io/github/mapepire_ibmi/Tls.java @@ -1,7 +1,25 @@ package io.github.mapepire_ibmi; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft_6455; +import org.java_websocket.handshake.ServerHandshake; + import io.github.mapepire_ibmi.types.DaemonServer; /** @@ -9,18 +27,74 @@ */ public class Tls { /** - * Get the SSL/TLS certificate from a specified DB2 server. - * - * This function establishes a secure connection to the server and retrieves the - * peer certificate information, which includes details about the server's - * SSL/TLS certificate. + * Get the SSL server certificate for a specified DB2 server. * - * @param creds The server details for the connection. - * @return A CompletableFuture that resolves to the detailed peer certificate - * information. + * @param db2Server The server details for the connection. + * @return A CompletableFuture that resolves to the SSL server certificate. */ - public CompletableFuture getCertificate(DaemonServer creds) { - // TODO: Add implementation - return CompletableFuture.completedFuture(null); + public static CompletableFuture getCertificate(DaemonServer db2Server) throws Exception { + X509TrustManager noAuthTrustManager = new X509TrustManager() { + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType) + throws CertificateException { + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType) + throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { noAuthTrustManager }, new SecureRandom()); + SSLContext.setDefault(sslContext); + URI uri = new URI("wss://" + db2Server.getHost() + ":" + db2Server.getPort() + "/db/"); + Map httpHeaders = new HashMap<>(); + String auth = db2Server.getUser() + ":" + db2Server.getPassword(); + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8)); + httpHeaders.put("Authorization", "Basic " + encodedAuth); + + CompletableFuture certificateFuture = new CompletableFuture<>(); + WebSocketClient wsc = new WebSocketClient(uri, new Draft_6455(), httpHeaders, 5000) { + @Override + public void onOpen(ServerHandshake handshakedata) { + try { + Certificate[] peerCertificates = this.getSSLSession().getPeerCertificates(); + byte[] encodedCa = peerCertificates[0].getEncoded(); + StringBuilder ca = new StringBuilder(); + ca.append("-----BEGIN CERTIFICATE-----\n"); + ca.append(Base64.getEncoder().encodeToString(encodedCa)); + ca.append("\n-----END CERTIFICATE-----\n"); + certificateFuture.complete(ca.toString()); + } catch (Exception e) { + certificateFuture.completeExceptionally(e); + } finally { + this.close(); + } + } + + @Override + public void onMessage(String message) { + } + + @Override + public void onClose(int code, String reason, boolean remote) { + } + + @Override + public void onError(Exception e) { + certificateFuture.completeExceptionally(e); + } + }; + SSLSocketFactory factory = sslContext.getSocketFactory(); + wsc.setSocketFactory(factory); + wsc.connect(); + + return certificateFuture; } } diff --git a/src/main/java/io/github/mapepire_ibmi/types/DaemonServer.java b/src/main/java/io/github/mapepire_ibmi/types/DaemonServer.java index 93fab8d..186619d 100644 --- a/src/main/java/io/github/mapepire_ibmi/types/DaemonServer.java +++ b/src/main/java/io/github/mapepire_ibmi/types/DaemonServer.java @@ -33,8 +33,8 @@ public class DaemonServer { /** * Whether to ignore unauthorized certificates. */ - @JsonProperty("ignoreUnauthorized") - private boolean ignoreUnauthorized; + @JsonProperty("rejectUnauthorized") + private boolean rejectUnauthorized = true; /** * The certificate authority (CA) for validating the server's certificate. @@ -49,6 +49,38 @@ public DaemonServer() { } + /** + * Construct a new DaemonServer instance. + * + * @param host The hostname or IP address of the server. + * @param port The port number to connect to. + * @param user The username for authentication. + * @param password The password for authentication. + */ + public DaemonServer(String host, int port, String user, String password) { + this.host = host; + this.port = port; + this.user = user; + this.password = password; + } + + /** + * Construct a new DaemonServer instance. + * + * @param host The hostname or IP address of the server. + * @param port The port number to connect to. + * @param user The username for authentication. + * @param password The password for authentication. + * @param rejectUnauthorized Whether to ignore unauthorized certificates. + */ + public DaemonServer(String host, int port, String user, String password, boolean rejectUnauthorized) { + this.host = host; + this.port = port; + this.user = user; + this.password = password; + this.rejectUnauthorized = rejectUnauthorized; + } + /** * Construct a new DaemonServer instance. * @@ -56,17 +88,17 @@ public DaemonServer() { * @param port The port number to connect to. * @param user The username for authentication. * @param password The password for authentication. - * @param ignoreUnauthorized Whether to ignore unauthorized certificates. + * @param rejectUnauthorized Whether to ignore unauthorized certificates. * @param ca The certificate authority (CA) for validating the * server's certificate. */ - public DaemonServer(String host, int port, String user, String password, boolean ignoreUnauthorized, + public DaemonServer(String host, int port, String user, String password, boolean rejectUnauthorized, String ca) { this.host = host; this.port = port; this.user = user; this.password = password; - this.ignoreUnauthorized = ignoreUnauthorized; + this.rejectUnauthorized = rejectUnauthorized; this.ca = ca; } @@ -147,17 +179,17 @@ public void setPassword(String password) { * * @return Whether to ignore unauthorized certificates. */ - public boolean getIgnoreUnauthorized() { - return ignoreUnauthorized; + public boolean getRejectUnauthorized() { + return rejectUnauthorized; } /** * Set whether to ignore unauthorized certificates. * - * @param ignoreUnauthorized Whether to ignore unauthorized certificates. + * @param rejectUnauthorized Whether to ignore unauthorized certificates. */ - public void setIgnoreUnauthorized(boolean ignoreUnauthorized) { - this.ignoreUnauthorized = ignoreUnauthorized; + public void setRejectUnauthorized(boolean rejectUnauthorized) { + this.rejectUnauthorized = rejectUnauthorized; } /** diff --git a/src/test/java/io/github/mapepire_ibmi/CLTest.java b/src/test/java/io/github/mapepire_ibmi/CLTest.java index 003758a..be3f499 100644 --- a/src/test/java/io/github/mapepire_ibmi/CLTest.java +++ b/src/test/java/io/github/mapepire_ibmi/CLTest.java @@ -20,7 +20,7 @@ void validCLCommand() throws Exception { job.close(); assertTrue(result.getSuccess()); - assertTrue(result.getData().size() > 0); + assertNotNull(result.getData()); } @Test diff --git a/src/test/java/io/github/mapepire_ibmi/ConnectTest.java b/src/test/java/io/github/mapepire_ibmi/ConnectTest.java index 7a1fdaf..d363b5f 100644 --- a/src/test/java/io/github/mapepire_ibmi/ConnectTest.java +++ b/src/test/java/io/github/mapepire_ibmi/ConnectTest.java @@ -1,6 +1,5 @@ package io.github.mapepire_ibmi; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -35,9 +34,8 @@ void invalidConnection() throws Exception { } }); - assertEquals( - "java.sql.SQLException: The application server rejected the connection. (User ID is not known.:FAKE_USER)", - e.getMessage()); + assertTrue(e.getMessage() + .contains("The application server rejected the connection.")); } @Test diff --git a/src/test/java/io/github/mapepire_ibmi/MapepireTest.java b/src/test/java/io/github/mapepire_ibmi/MapepireTest.java index c1460a5..43005d1 100644 --- a/src/test/java/io/github/mapepire_ibmi/MapepireTest.java +++ b/src/test/java/io/github/mapepire_ibmi/MapepireTest.java @@ -1,7 +1,6 @@ package io.github.mapepire_ibmi; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStream; import java.text.ParseException; import java.util.Arrays; @@ -17,16 +16,40 @@ @Timeout(25) class MapepireTest { - private static DaemonServer creds; - private static DaemonServer invalidCreds; - private static final String CONFIG_FILE = "config.properties"; + private static String host; + private static String user; + private static String password; + private static int port; + private static String invalidCA = "-----BEGIN CERTIFICATE-----\n" + + "MIIDtjCCAp6gAwIBAgIUaZwqO1YXrSGUZ+j2YlefGD+Li3UwDQYJKoZIhvcNAQEL\n" + + "BQAwcjELMAkGA1UEBhMCZHIxCzAJBgNVBAgMAnNlMQwwCgYDVQQHDAN0eXUxDDAK\n" + + "BgNVBAoMA3NlcjELMAkGA1UECwwCaGYxDzANBgNVBAMMBmRyZnRnZzEcMBoGCSqG\n" + + "SIb3DQEJARYNbG9nQGdtYWlsLmNvbTAeFw0yNDEwMDIxODU3MDFaFw0yNTEwMDIx\n" + + "ODU3MDFaMHQxCzAJBgNVBAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9y\n" + + "b250bzEMMAoGA1UECgwDSUJNMQswCQYDVQQLDAJMTDEMMAoGA1UEAwwDSk9OMR0w\n" + + "GwYJKoZIhvcNAQkBFg5mYWtlQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\n" + + "ggEPADCCAQoCggEBAI39BFoVAXIc0HFH7MgDAI53ExKgZkyBXGjFsooyzM/u215h\n" + + "QLtsfirmsFU8Kq2HOY1UaB1PRq4nrHYrwO7nYpmA14/9EgEXQsSRDH8LO18sSUQb\n" + + "hOX3622CunKDT77O7z5LakPuVvOsj1XBVTyFONTTzcuWW0mcnupj7j+WF+fldV3y\n" + + "Z64rfk/wJ2W1FjWNgxtm076KivVrV4RvL7DmGQH5sCENCx4eaGh2LGe1kb5yACQ3\n" + + "9zeC9aEwogEh2QRV8x3LzsroF3NR/IqzIm6L3kaiyWTsQkVlztmGpXY3WnFgfoBj\n" + + "e6IZOCRXzA9iTS1dRDGnFSzcRawf+PSIbP88LZ0CAwEAAaNCMEAwHQYDVR0OBBYE\n" + + "FH1B2+PDJmga5MwzswnukmSEt50OMB8GA1UdIwQYMBaAFPHGkLtIDz/tR3iBLgzU\n" + + "DjEK+tsoMA0GCSqGSIb3DQEBCwUAA4IBAQCnlEjRBF+IUNRfYVqOW4uHJriaBViu\n" + + "6zdXG+13pa7La+mAZ0BsoP1pLhrWjDul271MTOYsq429XBtlfxaNJiqHuPNjccKa\n" + + "wga2NFLAZriHYUvyP4Ld/H0IVAleIem4w2vwqHqayV2GeQCn5H+LknIaTzHKuRZ9\n" + + "fv6C/V5jBJFAJ29tYh79lioIRIZ6nzYLGWQIXbh9Y8uNIMbU3z4fqRQN65gKCkBB\n" + + "HaelrFfJI+UCGwOnr4qTKxkEB/lNz47O7kh4vmAk4mU3IsSWDMsydFHCTPLMg/Me\n" + + "TYn5iFqPQJhDoSiE8W0CeyAUXyhwWg7l9qiBaA+nI+t1Y307ld4T46x4\n" + + "-----END CERTIFICATE-----"; + private static String configFile = "config.properties"; @BeforeAll - public static void setup() throws IOException, ParseException { + public static void setup() throws Exception { Properties properties = new Properties(); - try (InputStream input = MapepireTest.class.getClassLoader().getResourceAsStream(CONFIG_FILE)) { + try (InputStream input = MapepireTest.class.getClassLoader().getResourceAsStream(configFile)) { if (input == null) { - throw new FileNotFoundException("Unable to find " + CONFIG_FILE); + throw new FileNotFoundException("Unable to find " + configFile); } properties.load(input); } @@ -41,22 +64,20 @@ public static void setup() throws IOException, ParseException { secrets.put(key, value); } - String host = secrets.get(keys.get(0)); - String user = secrets.get(keys.get(1)); - String password = secrets.get(keys.get(2)); - int port = Integer.parseInt(secrets.get(keys.get(3))); - - creds = new DaemonServer(host, port, user, password, true, ""); - invalidCreds = new DaemonServer(host, port, "FAKE_USER", "FAKE_PASSWORD", true, ""); - - // TODO: Get certificate + host = secrets.get(keys.get(0)); + user = secrets.get(keys.get(1)); + password = secrets.get(keys.get(2)); + port = Integer.parseInt(secrets.get(keys.get(3))); } - public static DaemonServer getCreds() { + public static DaemonServer getCreds() throws Exception { + DaemonServer creds = new DaemonServer(host, port, user, password); + String ca = Tls.getCertificate(creds).get(); + creds.setCa(ca); return creds; } public static DaemonServer getInvalidCreds() { - return invalidCreds; + return new DaemonServer(host, port, "FAKE_USER", "FAKE_PASSWORD", false, invalidCA); } } diff --git a/src/test/java/io/github/mapepire_ibmi/SqlTest.java b/src/test/java/io/github/mapepire_ibmi/SqlTest.java index 6bc3b7b..c70706d 100644 --- a/src/test/java/io/github/mapepire_ibmi/SqlTest.java +++ b/src/test/java/io/github/mapepire_ibmi/SqlTest.java @@ -72,7 +72,7 @@ void simpleQueryWithJDBCOptions() throws Exception { assertTrue(result.getHasResults()); assertNotNull(result.getMetadata()); assertTrue(result.getData().size() > 0); - assertEquals("[SQL5016] Qualified object name DEPARTMENT not valid., 42833, -5016", e.getMessage()); + assertTrue(e.getMessage().contains("[SQL5016] Qualified object name DEPARTMENT not valid.")); } @Test @@ -94,7 +94,7 @@ void simpleQueryInTerseFormat() throws Exception { assertNotNull(result.getMetadata()); assertFalse(result.getIsDone()); assertEquals(5, result.getData().size()); - assertEquals("NAME", row.get(0)); + assertNotNull(row.get(0)); } @Test @@ -133,9 +133,8 @@ void notExistentTableQuery() throws Exception { } }); - assertEquals( - "[SQL0204] SCOOBY in " + getCreds().getUser().toUpperCase() + " type *FILE not found., 42704, -204", - e.getMessage()); + assertTrue(e.getMessage() + .contains("[SQL0204] SCOOBY in " + getCreds().getUser().toUpperCase() + " type *FILE not found.")); } @Test @@ -155,7 +154,7 @@ void emptyQuery() throws Exception { } }); - assertEquals("A string parameter value with zero length was detected., 43617, -99999", e.getMessage()); + assertTrue(e.getMessage().contains("A string parameter value with zero length was detected.")); } @Test @@ -175,9 +174,8 @@ void invalidTokenQuery() throws Exception { } }); - assertEquals( - "[SQL0104] Token A was not valid. Valid tokens: ( CL END GET SET TAG CALL DROP FREE HOLD LOCK OPEN WITH ALTER., 42601, -104", - e.getMessage()); + assertTrue(e.getMessage().contains( + "[SQL0104] Token A was not valid. Valid tokens: ( CL END GET SET TAG CALL DROP FREE HOLD LOCK OPEN WITH ALTER.")); } @Test @@ -275,9 +273,9 @@ void executeOnPreparedQueryInTerseFormat() throws Exception { SqlJob job = new SqlJob(); job.connect(MapepireTest.getCreds()).get(); - QueryOptions options = new QueryOptions(true, false, Arrays.asList("PHONE")); + QueryOptions options = new QueryOptions(true, false, Arrays.asList("TABLE_NAME")); Query query = job.query("SELECT * FROM SAMPLE.SYSCOLUMNS WHERE COLUMN_NAME = ?", options); - QueryResult result = query.execute().get(); + QueryResult result = query.execute(1).get(); ArrayList row = (ArrayList) result.getData().get(0); query.close().get(); @@ -287,9 +285,9 @@ void executeOnPreparedQueryInTerseFormat() throws Exception { assertNotNull(result.getId()); assertTrue(result.getHasResults()); assertNotNull(result.getMetadata()); - assertTrue(result.getIsDone()); + assertFalse(result.getIsDone()); assertEquals(1, result.getData().size()); - assertEquals("PHONE", row.get(0)); + assertEquals("TABLE_NAME", row.get(0)); } @Test @@ -331,9 +329,8 @@ void executeOnNoParameterPreparedQuery() throws Exception { } }); - assertEquals( - "The number of parameter values set or registered does not match the number of parameters., 07001, -99999", - e.getMessage()); + assertTrue(e.getMessage().contains( + "The number of parameter values set or registered does not match the number of parameters.")); } @Test @@ -354,7 +351,7 @@ void executeOnWrongParameterCountPreparedQuery() throws Exception { } }); - assertEquals("Descriptor index not valid. (2>1), 07009, -99999", e.getMessage()); + assertTrue(e.getMessage().contains("Descriptor index not valid. (2>1)")); } @Test @@ -375,9 +372,7 @@ void executeOnInvalidPreparedQuery() throws Exception { } }); - assertEquals( - "[SQL0204] FAKE_TABLE in FAKE_SCHEMA type *FILE not found., 42704, -204", - e.getMessage()); + assertTrue(e.getMessage().contains("[SQL0204] FAKE_TABLE in FAKE_SCHEMA type *FILE not found.")); } @Test diff --git a/src/test/java/io/github/mapepire_ibmi/TlsTest.java b/src/test/java/io/github/mapepire_ibmi/TlsTest.java index 380144b..bfe467d 100644 --- a/src/test/java/io/github/mapepire_ibmi/TlsTest.java +++ b/src/test/java/io/github/mapepire_ibmi/TlsTest.java @@ -1,15 +1,31 @@ package io.github.mapepire_ibmi; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.ExecutionException; + import org.junit.jupiter.api.Test; +import io.github.mapepire_ibmi.types.DaemonServer; + class TlsTest extends MapepireTest { @Test - void canGetCertCorrectly() { - // TODO: + void canGetCertCorrectly() throws Exception { + String ca = Tls.getCertificate(MapepireTest.getCreds()).get(); + + assertNotNull(ca); } @Test - void willFailCorrectly() { - // TODO: + void failsToGetCertCorrectly() throws Exception { + ExecutionException e = assertThrowsExactly(ExecutionException.class, () -> { + DaemonServer invalidCreds = MapepireTest.getInvalidCreds(); + invalidCreds.setHost("FAKE_HOST"); + Tls.getCertificate(invalidCreds).get(); + }); + + assertTrue(e.getMessage().contains("Connection refused")); } }