cli/azd/internal/appdetect/java.go (135 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package appdetect import ( "context" "encoding/xml" "fmt" "io/fs" "maps" "path/filepath" "slices" "strings" "github.com/azure/azure-dev/cli/azd/pkg/tools/maven" ) type javaDetector struct { mvnCli *maven.Cli rootProjects []mavenProject } func (jd *javaDetector) Language() Language { return Java } func (jd *javaDetector) DetectProject(ctx context.Context, path string, entries []fs.DirEntry) (*Project, error) { for _, entry := range entries { if strings.ToLower(entry.Name()) == "pom.xml" { pomFile := filepath.Join(path, entry.Name()) project, err := readMavenProject(ctx, jd.mvnCli, pomFile) if err != nil { return nil, fmt.Errorf("error reading pom.xml: %w", err) } if len(project.Modules) > 0 { // This is a multi-module project, we will capture the analysis, but return nil // to continue recursing jd.rootProjects = append(jd.rootProjects, *project) return nil, nil } // the absolute root project var root *mavenProject current := project for { newRoot := false for _, rootProject := range jd.rootProjects { for _, module := range rootProject.Modules { if filepath.Join(rootProject.path, module) == current.path { root = &rootProject current = root newRoot = true } } } if !newRoot { // we iterated and didn't find a new parent, there is either no root or we've found it break } } result, err := detectDependencies(project, &Project{ Language: Java, Path: path, DetectionRule: "Inferred by presence of: pom.xml", }) if err != nil { return nil, fmt.Errorf("detecting dependencies: %w", err) } if root != nil { result.RootPath = root.path } return result, nil } } return nil, nil } // mavenProject represents the top-level structure of a Maven POM file. type mavenProject struct { XmlName xml.Name `xml:"project"` Parent parent `xml:"parent"` Modules []string `xml:"modules>module"` // Capture the modules Dependencies []dependency `xml:"dependencies>dependency"` DependencyManagement dependencyManagement `xml:"dependencyManagement"` Build build `xml:"build"` path string } // Parent represents the parent POM if this project is a module. type parent struct { GroupId string `xml:"groupId"` ArtifactId string `xml:"artifactId"` Version string `xml:"version"` } // Dependency represents a single Maven dependency. type dependency struct { GroupId string `xml:"groupId"` ArtifactId string `xml:"artifactId"` Version string `xml:"version"` Scope string `xml:"scope,omitempty"` } // DependencyManagement includes a list of dependencies that are managed. type dependencyManagement struct { Dependencies []dependency `xml:"dependencies>dependency"` } // Build represents the build configuration which can contain plugins. type build struct { Plugins []plugin `xml:"plugins>plugin"` } // Plugin represents a build plugin. type plugin struct { GroupId string `xml:"groupId"` ArtifactId string `xml:"artifactId"` Version string `xml:"version"` } func readMavenProject(ctx context.Context, mvnCli *maven.Cli, filePath string) (*mavenProject, error) { effectivePom, err := mvnCli.EffectivePom(ctx, filePath) if err != nil { return nil, err } var project mavenProject if err := xml.Unmarshal([]byte(effectivePom), &project); err != nil { return nil, fmt.Errorf("parsing xml: %w", err) } project.path = filepath.Dir(filePath) return &project, nil } func detectDependencies(mavenProject *mavenProject, project *Project) (*Project, error) { databaseDepMap := map[DatabaseDep]struct{}{} for _, dep := range mavenProject.Dependencies { if (dep.GroupId == "com.mysql" && dep.ArtifactId == "mysql-connector-j") || (dep.GroupId == "com.azure.spring" && dep.ArtifactId == "spring-cloud-azure-starter-jdbc-mysql") { databaseDepMap[DbMySql] = struct{}{} } if (dep.GroupId == "org.postgresql" && dep.ArtifactId == "postgresql") || (dep.GroupId == "com.azure.spring" && dep.ArtifactId == "spring-cloud-azure-starter-jdbc-postgresql") { databaseDepMap[DbPostgres] = struct{}{} } if (dep.GroupId == "org.springframework.boot" && dep.ArtifactId == "spring-boot-starter-data-redis") || (dep.GroupId == "org.springframework.boot" && dep.ArtifactId == "spring-boot-starter-data-redis-reactive") { databaseDepMap[DbRedis] = struct{}{} } if (dep.GroupId == "org.springframework.boot" && dep.ArtifactId == "spring-boot-starter-data-mongodb") || (dep.GroupId == "org.springframework.boot" && dep.ArtifactId == "spring-boot-starter-data-mongodb-reactive") { databaseDepMap[DbMongo] = struct{}{} } // todo: Add DbCosmos } if len(databaseDepMap) > 0 { project.DatabaseDeps = slices.SortedFunc(maps.Keys(databaseDepMap), func(a, b DatabaseDep) int { return strings.Compare(string(a), string(b)) }) } return project, nil }