diff --git a/recipes/hikariucp/.gitignore b/recipes/hikariucp/.gitignore new file mode 100644 index 00000000..dc9153d0 --- /dev/null +++ b/recipes/hikariucp/.gitignore @@ -0,0 +1,42 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +*.log + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +.idea +.idea/** + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +/.jpb/** + +### Mac OS ### +.DS_Store diff --git a/recipes/hikariucp/README.md b/recipes/hikariucp/README.md new file mode 100644 index 00000000..659fc487 --- /dev/null +++ b/recipes/hikariucp/README.md @@ -0,0 +1,124 @@ +# Hikari to UCP Open Rewrite Recipe + +Current Version is `0.0.1-SNAPSHOT` and is under development. Please file GitHub issues for issues, enhancements and more. + +## Hikari to UCP rewrite Recipe + +> **_NOTE:_** In the pre release the rewrite works best with `properties` files. `YAML` files works too but the formatting of the outcome isn't pretty. + +This recipe will change the Hikare connection pool parameters and dependency from Hikari to Oracle Universal Connection Pooling (UCP). The [UCP documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjucp/index.html). + +The following properties are rewritten: + +### Maven dependencies `pom.xml` + +The Hikari dependency is removed and replaced with SPring Boot starters for Oracle UCP and Oracle Wallet (commonly used to connect to an autonomous database (ADB)). + +```xml + + com.zaxxer + HikariCP + +``` + +is changed to: + +```xml + + com.oracle.database.spring + oracle-spring-boot-starter-ucp + 25.1.0 + +``` + +The following dependency is removed (all ojdbc* variants) and replaced by the UCP Starter: + +```xml + + com.oracle.database.jdbc + ojdbc* + +``` + +And the following dependency is added, as it is commonly used to connect to an autonomous database (ADB): + +```xml + + com.oracle.database.spring + oracle-spring-boot-starter-wallet + 25.1.0 + +``` + +### Spring Boot Connection Pooling configuration `application.properties` + +> **_NOTE:_** The recipe will change Hikari's milliseconds to seconds. + +The following properties are rewritten. [UCP documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjucp/index.html) + +| Hikari Property | Oracle UCP Property | Notes | +|-----------------|---------------------|-------| +| N/A | `spring.datasource.driver-class-name` | Will be set to `oracle.jdbc.OracleDriver` | +| N/A | `spring.datasource.type` | Will be set to `oracle.ucp.jdbc.PoolDataSource` | +| `spring.datasource.hikari.pool-name` | `spring.datasource.oracleucp.connection-pool-name` | | +| `spring.datasource.hikari.maximum-pool-size` | `spring.datasource.oracleucp.max-pool-size` | | +| `spring.datasource.hikari.minimum-idle` | `spring.datasource.oracleucp.min-pool-size` | | +| `spring.datasource.hikari.connection-timeout` | `spring.datasource.oracleucp.connection-wait-timeout` | | +| `spring.datasource.hikari.idle-timeout` | `spring.datasource.oracleucp.inactive-connection-timeout` | | +| `spring.datasource.hikari.connection-test-query` | `spring.datasource.oracleucp.s-q-l-for-validate-connection` | | +| `spring.datasource.hikari.max-lifetime` | `spring.datasource.oracleucp.max-connection-reuse-time` | | +| `spring.datasource.hikari.validation-timeout` | `spring.datasource.oracleucp.connection-validation-timeout` | | + +The following UCP properties are added: + +| Oracle UCP Property | Value | Notes | +|---------------------|-------|-------| +| `spring.datasource.oracleucp.initial-pool-size` | 5 | [UCP documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjucp/index.html) | + +The following Hikari Properties do not have identical UCP properties and will be commented out in the rewritten properties file. [UCP documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjucp/index.html) + +| Hikari UCP Property | Notes | +|---------------------|-------| +| `spring.datasource.hikari.auto-commit` | Use Oracle JDBC driver connection property autoCommit | +| `spring.datasource.hikari.register-mbeans` | UCP always attempts registration | +| `spring.datasource.hikari.thread-factory` | UCP supports setTaskManager instead | +| `spring.datasource.hikari.scheduled-executor` | UCP supports setTaskManager instead | +| `spring.datasource.hikari.keepalive-time` | Closest is to use driver connection properties oracle.net.keepAlive + oracle.net.TCP_KEEPIDLE | + +## Build the Open Rewrite Recipe + +The repo is using maven. To build the recipe run the following command: + +```shell +mvn install +``` + +## To use the Recipe + + In the repository you want to test your recipe against, update the `pom.xml` to include the following: + + ```xml + + + + + org.openrewrite.maven + rewrite-maven-plugin + 6.3.2 + + + ConvertHikariToUCP + + + + + com.oracle.cloud.recipes + hikariucp + 0.0.1-SNAPSHOT + + + + + + +``` diff --git a/recipes/hikariucp/pom.xml b/recipes/hikariucp/pom.xml new file mode 100644 index 00000000..710aea3c --- /dev/null +++ b/recipes/hikariucp/pom.xml @@ -0,0 +1,134 @@ + + + + + 4.0.0 + + com.oracle.cloud.recipes + hikariucp + 0.0.1-SNAPSHOT + hikari-ucp + Openrewrite Recipe to convert Hikari Connection Pool to Oracle UCP + + https://github.com/oracle/spring-cloud-oracle/tree/hikari-ucp-recipe/recipes/hikari-ucp + + + Oracle America, Inc. + https://www.oracle.com + + + + The Universal Permissive License (UPL), Version 1.0 + https://oss.oracle.com/licenses/upl/ + repo + + + + + Oracle + obaas_ww at oracle.com + Oracle America, Inc. + https://www.oracle.com + + + + https://github.com/oracle/spring-cloud-oracle + scm:git:https://github.com/oracle/spring-cloud-oracle.git + scm:git:git@github.com:oracle/spring-cloud-oracle.git + + + + 17 + 17 + 17 + UTF-8 + 8.48.1 + 3.3.0 + 6.3.2 + 5.12.1 + + + + + org.openrewrite + rewrite-java + ${rewrite.version} + + + + org.openrewrite + rewrite-properties + ${rewrite.version} + + + + org.openrewrite + rewrite-yaml + ${rewrite.version} + + + + org.openrewrite + rewrite-maven + ${rewrite.version} + + + + org.junit.jupiter + junit-jupiter + test + + + + org.openrewrite + rewrite-test + test + + + + + + + org.openrewrite.recipe + rewrite-recipe-bom + ${rewrite.recipe.version} + pom + import + + + org.junit + junit-bom + ${junit.version} + pom + import + + + + + + + + org.openrewrite.maven + rewrite-maven-plugin + ${maven.rewrite.plugin.version} + + + + + + + + + org.openrewrite.recipe + rewrite-spring + 5.24.1 + + + + + + + + \ No newline at end of file diff --git a/recipes/hikariucp/src/main/com/oracle/cloud/recipes/hikariucp/ConvertMsToSecondsInPropertiesRecipe.java b/recipes/hikariucp/src/main/com/oracle/cloud/recipes/hikariucp/ConvertMsToSecondsInPropertiesRecipe.java new file mode 100644 index 00000000..ed9004ef --- /dev/null +++ b/recipes/hikariucp/src/main/com/oracle/cloud/recipes/hikariucp/ConvertMsToSecondsInPropertiesRecipe.java @@ -0,0 +1,57 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +package com.oracle.cloud.recipes.hikariucp; + +import org.openrewrite.NlsRewrite; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.properties.PropertiesVisitor; +import org.openrewrite.properties.tree.Properties; + +public class ConvertMsToSecondsInPropertiesRecipe extends Recipe { + + private final String keyRegex; + + // Takes a keyRegex parameter to specify which property keys to target (e.g., Hikari timeout properties). + public ConvertMsToSecondsInPropertiesRecipe(String keyRegex) { + this.keyRegex = keyRegex; + } + + // Extends PropertiesVisitor to process each Properties.Entry (key-value pair) in application.properties. + @Override + public TreeVisitor getVisitor() { + + return new PropertiesVisitor() { + + @Override + public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { + if (entry.getKey().matches(keyRegex)) { + String value = entry.getValue().getText().trim(); + try { + long ms = Long.parseLong(value); + double seconds = ms / 1000.0; + String newValue = String.valueOf(seconds); + Properties.Value updatedValue = entry.getValue().withText(newValue); + return entry.withValue(updatedValue); + } catch (NumberFormatException e) { + // If the value isn't a valid number, ignore it. + } + } + return super.visitEntry(entry, ctx); + } + + }; + } + + @Override + public @NlsRewrite.DisplayName String getDisplayName() { + return "Convert milliseconds to seconds for Hikari properties"; + } + + @Override + public @NlsRewrite.Description String getDescription() { + return "Transforms millisecond values to seconds for Hikari connection pool properties matching the given regex."; + } +} diff --git a/recipes/hikariucp/src/main/com/oracle/cloud/recipes/hikariucp/ConvertMsToSecondsInYamlRecipe.java b/recipes/hikariucp/src/main/com/oracle/cloud/recipes/hikariucp/ConvertMsToSecondsInYamlRecipe.java new file mode 100644 index 00000000..b605cda9 --- /dev/null +++ b/recipes/hikariucp/src/main/com/oracle/cloud/recipes/hikariucp/ConvertMsToSecondsInYamlRecipe.java @@ -0,0 +1,68 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +package com.oracle.cloud.recipes.hikariucp; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.NlsRewrite; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.yaml.YamlVisitor; +import org.openrewrite.yaml.tree.Yaml; + +import java.util.regex.Pattern; + +public class ConvertMsToSecondsInYamlRecipe extends Recipe { + + private final String pathRegex; + + public ConvertMsToSecondsInYamlRecipe(String pathRegex) { + this.pathRegex = pathRegex; + } + + @Override + public TreeVisitor getVisitor() { + + Pattern pattern = Pattern.compile(pathRegex); + + return new YamlVisitor() { + private String currentPath = ""; + + @Override + public Yaml visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) { + String previousPath = currentPath; + currentPath += (currentPath.isEmpty() ? "" : ".") + entry.getKey().getValue(); + Yaml.Mapping.Entry updatedEntry = (Yaml.Mapping.Entry) super.visitMappingEntry(entry, ctx); + currentPath = previousPath; + return updatedEntry; + } + + @Override + public Yaml visitScalar(Yaml.Scalar scalar, ExecutionContext ctx) { + if (pattern.matcher(currentPath).matches()) { + String value = scalar.getValue().trim(); + try { + long ms = Long.parseLong(value); + double seconds = ms / 1000.0; + String newValue = String.valueOf(seconds); + return scalar.withValue(newValue); + } catch (NumberFormatException e) { + // If the value isn't a valid number, ignore it. + } + } + return super.visitScalar(scalar, ctx); + } + }; + + } + + @Override + public @NlsRewrite.DisplayName String getDisplayName() { + return ""; + } + + @Override + public @NlsRewrite.Description String getDescription() { + return ""; + } +} diff --git a/recipes/hikariucp/src/main/resources/META-INF/rewrite/rewrite.yml b/recipes/hikariucp/src/main/resources/META-INF/rewrite/rewrite.yml new file mode 100644 index 00000000..8d0964db --- /dev/null +++ b/recipes/hikariucp/src/main/resources/META-INF/rewrite/rewrite.yml @@ -0,0 +1,188 @@ +# +# Copyright © 2025, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +# +# Source for the recipe: +# https://github.com/brettwooldridge/HikariCP?tab=readme-ov-file#gear-configuration-knobs-baby +--- +type: specs.openrewrite.org/v1beta/recipe +name: com.oracle.cloud.recipes.hikariucp.ConvertHikariToUCP +displayName: Migrate Hikari CP to Oracle UCP +description: Migrate Hikari Connection Pool to Oracle Universal Connection Pool +tags: + - spring + - oracle + - ucp + - hikari +recipeList: + + # For pom.xml + - org.openrewrite.java.dependencies.RemoveDependency: + GroupId: com.oracle.database.jdbc + ArtifactId: ojdbc* + - org.openrewrite.java.dependencies.RemoveDependency: + groupId: com.zaxxer* + artifactId: HikariCP* + - org.openrewrite.java.dependencies.AddDependency: + groupId: com.oracle.database.spring + artifactId: oracle-spring-boot-starter-ucp + version: 25.1.0 + - org.openrewrite.java.dependencies.AddDependency: + groupId: com.oracle.database.spring + artifactId: oracle-spring-boot-starter-wallet + version: 25.1.0 + + # https://docs.openrewrite.org/recipes/java/spring/addspringproperty + # Add a spring configuration property to a configuration file if it does not already exist in that file. + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.driver-class-name + value: oracle.jdbc.OracleDriver + comment: "JDBC driver class" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.type + value: oracle.ucp.jdbc.PoolDataSource + comment: specify the connection pool driver to use" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.connection-pool-name + value: UCPConnectionPool + comment: "Connection Pool Name" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.max-pool-size + value: 20 + comment: "Specifies the maximum number of available and borrowed connections that our pool is maintaining" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.min-pool-size + value: 5 + comment: "Specifies the minimum number of available and borrowed connections that our pool is maintaining" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.initial-pool-size + value: 10 + comment: "Specifies the number of available connections created after the pool is initiated" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.connection-wait-timeout + value: 3 + comment: "specifies how long an application request waits to obtain a connection if there are no longer any connections in the pool" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.inactive-connection-timeout + value: 0 + comment: "Specifies how long an available connection can remain idle before it is closed and removed from the pool" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.max-connection-reuse-time + value: 0 + comment: "Allows connections to be gracefully closed and removed from a connection pool after a specific amount of time" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.connection-validation-timeout + value: 15 + comment: "Specifies the duration within which a borrowed connection from the pool is validated" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.fast-connection-failover-enabled + value: false + comment: "Enables faster failover for connection attempts" + pathExpressions: ["**/application.properties"] + + - org.openrewrite.java.spring.AddSpringProperty: + property: spring.datasource.oracleucp.validate-connection-on-borrow + value: false + comment: "Whether to validate connections when borrowed from the pool" + pathExpressions: ["**/application.properties"] + + # spring.datasource.oracleucp.connection-factory-class: oracle.jdbc.pool.OracleDataSource + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.data-source-class-name + newPropertyKey: spring.datasource.oracleucp.connection-factory-class-name + - org.openrewrite.java.spring.ChangeSpringPropertyValue: + propertyKey: spring.datasource.oracleucp.connection-factory-class-name + newValue: oracle.jdbc.pool.OracleDataSource + + # spring.datasource.driver-class-name: OracleDriver + - org.openrewrite.java.spring.ChangeSpringPropertyValue: + propertyKey: spring.datasource.driver-class-name + newValue: oracle.jdbc.OracleDriver + + # spring.datasource.type: oracle.ucp.jdbc.PoolDataSource + - org.openrewrite.java.spring.ChangeSpringPropertyValue: + propertyKey: spring.datasource.type + newValue: oracle.ucp.jdbc.PoolDataSource + + # spring.datasource.oracleucp.connection-pool-name + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.pool-name + newPropertyKey: spring.datasource.oracleucp.connection-pool-name + + # spring.datasource.oracleucp.max-pool-size + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.maximum-pool-size + newPropertyKey: spring.datasource.oracleucp.max-pool-size + + # spring.datasource.oracleucp.min-pool-size + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.minimum-idle + newPropertyKey: spring.datasource.oracleucp.min-pool-size + + # spring.datasource.oracleucp.connection-validation-timeout + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.connection-timeout + newPropertyKey: spring.datasource.oracleucp.connection-wait-timeout + + # spring.datasource.oracleucp.inactive-connection-timeout + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.idle-timeout + newPropertyKey: spring.datasource.oracleucp.inactive-connection-timeout + + # spring.datasource.oracleucp.s-q-l-for-validate-connection + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.connection-test-query + newPropertyKey: spring.datasource.oracleucp.s-q-l-for-validate-connection + + # spring.datasource.oracleucp.max-connection-reuse-time + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.max-lifetime + newPropertyKey: spring.datasource.oracleucp.max-connection-reuse-time + + # spring.datasource.oracleucp.max-connection-reuse-time + - org.openrewrite.java.spring.ChangeSpringPropertyKey: + oldPropertyKey: spring.datasource.hikari.validation-timeout + newPropertyKey: spring.datasource.oracleucp.connection-validation-timeout + + # HikariCP properties that don’t have identical UCP properties + - org.openrewrite.java.spring.CommentOutSpringPropertyKey: + propertyKey: spring.datasource.hikari.auto-commit + comment: "Use Oracle JDBC driver connection property autoCommit." + - org.openrewrite.java.spring.CommentOutSpringPropertyKey: + propertyKey: spring.datasource.hikari.register-mbeans + comment: "UCP always attempts registration." + - org.openrewrite.java.spring.CommentOutSpringPropertyKey: + propertyKey: spring.datasource.hikari.thread-factory + comment: "UCP supports setTaskManager instead." + - org.openrewrite.java.spring.CommentOutSpringPropertyKey: + propertyKey: spring.datasource.hikari.scheduled-executor + comment: "UCP supports setTaskManager instead." + - org.openrewrite.java.spring.CommentOutSpringPropertyKey: + propertyKey: spring.datasource.hikari.keepalive-time + comment: "Closest is to use driver connection properties oracle.net.keepAlive + oracle.net.TCP_KEEPIDLE" + + # Convert milliseconds to seconds + - oracle.com.cloud.recipes.hikariucp.ConvertMsToSecondsInPropertiesRecipe: + keyRegex: 'spring\.datasource\.hikari\.(connectionTimeout|idleTimeout|maxLifetime|leakDetectionThreshold)' + - oracle.com.cloud.recipes.hikariucp.ConvertMsToSecondsInYamlRecipe: + pathRegex: 'spring\.datasource\.hikari\.(connectionTimeout|idleTimeout|maxLifetime|leakDetectionThreshold)' \ No newline at end of file diff --git a/recipes/hikariucp/src/test/oracle/com/cloud/recipes/hikariucp/ChangePropertyValueTest.java b/recipes/hikariucp/src/test/oracle/com/cloud/recipes/hikariucp/ChangePropertyValueTest.java new file mode 100644 index 00000000..71bb8187 --- /dev/null +++ b/recipes/hikariucp/src/test/oracle/com/cloud/recipes/hikariucp/ChangePropertyValueTest.java @@ -0,0 +1,22 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +package oracle.com.cloud.recipes.hikariucp; + +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.properties.Assertions.properties; +import static org.openrewrite.yaml.Assertions.yaml; + +class ChangeSpringPropertyValueTest implements RewriteTest { + + // @DocumentExample + // @Test + // void propFile() { + // rewriteRun( + // spec -> spec.recipe(new ChangeSpringPropertyValue("server.port", "8081", null, null, null)), + // properties("server.port=8080", "server.port=8081") + // ); + + +} \ No newline at end of file