diff --git a/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring.go b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring.go index eef378a9836..3370477b551 100644 --- a/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring.go +++ b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring.go @@ -1,28 +1,44 @@ package javaanalyze import ( + "bufio" "fmt" "gopkg.in/yaml.v3" - "io/ioutil" "log" + "os" + "path/filepath" + "strings" ) type springProject struct { - applicationProperties map[string]interface{} + applicationProperties map[string]string } func analyzeSpringProject(projectPath string) springProject { return springProject{ - applicationProperties: findSpringApplicationProperties(projectPath), + applicationProperties: getProperties(projectPath), } } -func findSpringApplicationProperties(projectPath string) map[string]interface{} { - yamlFilePath := projectPath + "/src/main/resources/application.yml" - data, err := ioutil.ReadFile(yamlFilePath) +func getProperties(projectPath string) map[string]string { + result := make(map[string]string) + getPropertiesInPropertiesFile(filepath.Join(projectPath, "/src/main/resources/application.properties"), result) + getPropertiesInYamlFile(filepath.Join(projectPath, "/src/main/resources/application.yml"), result) + getPropertiesInYamlFile(filepath.Join(projectPath, "/src/main/resources/application.yaml"), result) + profile, profileSet := result["spring.profiles.active"] + if profileSet { + getPropertiesInPropertiesFile(filepath.Join(projectPath, "/src/main/resources/application-"+profile+".properties"), result) + getPropertiesInYamlFile(filepath.Join(projectPath, "/src/main/resources/application-"+profile+".yml"), result) + getPropertiesInYamlFile(filepath.Join(projectPath, "/src/main/resources/application-"+profile+".yaml"), result) + } + return result +} + +func getPropertiesInYamlFile(yamlFilePath string, result map[string]string) { + data, err := os.ReadFile(yamlFilePath) if err != nil { - log.Printf("failed to read spring application properties: %s", yamlFilePath) - return nil + // Ignore the error if file not exist. + return } // Parse the YAML into a yaml.Node @@ -32,14 +48,11 @@ func findSpringApplicationProperties(projectPath string) map[string]interface{} log.Fatalf("error unmarshalling YAML: %v", err) } - result := make(map[string]interface{}) parseYAML("", &root, result) - - return result } // Recursively parse the YAML and build dot-separated keys into a map -func parseYAML(prefix string, node *yaml.Node, result map[string]interface{}) { +func parseYAML(prefix string, node *yaml.Node, result map[string]string) { switch node.Kind { case yaml.DocumentNode: // Process each document's content @@ -77,3 +90,26 @@ func parseYAML(prefix string, node *yaml.Node, result map[string]interface{}) { // Handle other node types if necessary } } + +func getPropertiesInPropertiesFile(propertiesFilePath string, result map[string]string) { + file, err := os.Open(propertiesFilePath) + if err != nil { + // Ignore the error if file not exist. + return + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.TrimSpace(line) == "" || strings.HasPrefix(line, "#") { + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + result[key] = value + } + } +} diff --git a/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring_test.go b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring_test.go new file mode 100644 index 00000000000..833645e4b52 --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring_test.go @@ -0,0 +1,26 @@ +package javaanalyze + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAnalyzeSpringProject(t *testing.T) { + var project = analyzeSpringProject(filepath.Join("testdata", "project-one")) + require.Equal(t, "", project.applicationProperties["not.exist"]) + require.Equal(t, "jdbc:h2:mem:testdb", project.applicationProperties["spring.datasource.url"]) + + project = analyzeSpringProject(filepath.Join("testdata", "project-two")) + require.Equal(t, "", project.applicationProperties["not.exist"]) + require.Equal(t, "jdbc:h2:mem:testdb", project.applicationProperties["spring.datasource.url"]) + + project = analyzeSpringProject(filepath.Join("testdata", "project-three")) + require.Equal(t, "", project.applicationProperties["not.exist"]) + require.Equal(t, "HTML", project.applicationProperties["spring.thymeleaf.mode"]) + + project = analyzeSpringProject(filepath.Join("testdata", "project-four")) + require.Equal(t, "", project.applicationProperties["not.exist"]) + require.Equal(t, "mysql", project.applicationProperties["database"]) +} diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_servicebus_scsb.go b/cli/azd/internal/appdetect/javaanalyze/rule_servicebus_scsb.go index 4276527b56d..242d22560ff 100644 --- a/cli/azd/internal/appdetect/javaanalyze/rule_servicebus_scsb.go +++ b/cli/azd/internal/appdetect/javaanalyze/rule_servicebus_scsb.go @@ -22,7 +22,7 @@ func (r *ruleServiceBusScsb) match(javaProject *javaProject) bool { } // Function to find all properties that match the pattern `spring.cloud.stream.bindings..destination` -func findBindingDestinations(properties map[string]interface{}) map[string]string { +func findBindingDestinations(properties map[string]string) map[string]string { result := make(map[string]string) // Iterate through the properties map and look for matching keys diff --git a/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application-mysql.properties b/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application-mysql.properties new file mode 100644 index 00000000000..33ec21d3c95 --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application-mysql.properties @@ -0,0 +1,7 @@ +# database init, supports mysql too +database=mysql +spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:petclinic} +spring.datasource.username=${MYSQL_USERNAME:petclinic} +spring.datasource.password=${MYSQL_PASSWORD:} +# SQL is written to be idempotent so this is safe +spring.sql.init.mode=always diff --git a/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application-postgres.properties b/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application-postgres.properties new file mode 100644 index 00000000000..7d9676e3aad --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application-postgres.properties @@ -0,0 +1,6 @@ +database=postgres +spring.datasource.url=jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_HOST:5432}/${POSTGRES_DATABASE:petclinic} +spring.datasource.username=${POSTGRES_USERNAME:petclinic} +spring.datasource.password=${POSTGRES_PASSWORD:} +# SQL is written to be idempotent so this is safe +spring.sql.init.mode=always diff --git a/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application.properties b/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application.properties new file mode 100644 index 00000000000..59d5368e73c --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/testdata/project-four/src/main/resources/application.properties @@ -0,0 +1,29 @@ +# database init, supports mysql too +database=h2 +spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql +spring.sql.init.data-locations=classpath*:db/${database}/data.sql + +# Web +spring.thymeleaf.mode=HTML + +# JPA +spring.jpa.hibernate.ddl-auto=none +spring.jpa.open-in-view=true + +# Internationalization +spring.messages.basename=messages/messages + +spring.profiles.active=mysql + +# Actuator +management.endpoints.web.exposure.include=* + +# Logging +logging.level.org.springframework=INFO +# logging.level.org.springframework.web=DEBUG +# logging.level.org.springframework.context.annotation=TRACE + +# Maximum time static resources should be cached +spring.web.resources.cache.cachecontrol.max-age=12h + +server.port=8081 diff --git a/cli/azd/internal/appdetect/javaanalyze/testdata/project-one/src/main/resources/application.yml b/cli/azd/internal/appdetect/javaanalyze/testdata/project-one/src/main/resources/application.yml new file mode 100644 index 00000000000..09d0cc057c5 --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/testdata/project-one/src/main/resources/application.yml @@ -0,0 +1,12 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + jackson: + date-format: com.microsoft.azure.simpletodo.configuration.RFC3339DateFormat + serialization: + write-dates-as-timestamps: false + jpa: + hibernate: + ddl-auto: update + show-sql: true + diff --git a/cli/azd/internal/appdetect/javaanalyze/testdata/project-three/src/main/resources/application.properties b/cli/azd/internal/appdetect/javaanalyze/testdata/project-three/src/main/resources/application.properties new file mode 100644 index 00000000000..59d5368e73c --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/testdata/project-three/src/main/resources/application.properties @@ -0,0 +1,29 @@ +# database init, supports mysql too +database=h2 +spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql +spring.sql.init.data-locations=classpath*:db/${database}/data.sql + +# Web +spring.thymeleaf.mode=HTML + +# JPA +spring.jpa.hibernate.ddl-auto=none +spring.jpa.open-in-view=true + +# Internationalization +spring.messages.basename=messages/messages + +spring.profiles.active=mysql + +# Actuator +management.endpoints.web.exposure.include=* + +# Logging +logging.level.org.springframework=INFO +# logging.level.org.springframework.web=DEBUG +# logging.level.org.springframework.context.annotation=TRACE + +# Maximum time static resources should be cached +spring.web.resources.cache.cachecontrol.max-age=12h + +server.port=8081 diff --git a/cli/azd/internal/appdetect/javaanalyze/testdata/project-two/src/main/resources/application.yaml b/cli/azd/internal/appdetect/javaanalyze/testdata/project-two/src/main/resources/application.yaml new file mode 100644 index 00000000000..09d0cc057c5 --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/testdata/project-two/src/main/resources/application.yaml @@ -0,0 +1,12 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + jackson: + date-format: com.microsoft.azure.simpletodo.configuration.RFC3339DateFormat + serialization: + write-dates-as-timestamps: false + jpa: + hibernate: + ddl-auto: update + show-sql: true +