Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 155 additions & 5 deletions cli/azd/internal/appdetect/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ package appdetect

import (
"context"
"encoding/xml"
"fmt"
"io/fs"
"maps"
"os"
"path/filepath"
"slices"
"strings"
)

Expand All @@ -16,13 +22,157 @@ func (jd *javaDetector) Language() Language {
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" {
return &Project{
Language: Java,
Path: path,
DetectionRule: "Inferred by presence of: " + entry.Name(),
}, nil
project, err := analyzeJavaProject(path)
if err != nil {
return nil, err
}
return project, nil
}
}

return nil, nil
}

type javaProject struct {
mavenProject mavenProject
}
Comment thread
saragluna marked this conversation as resolved.
Outdated

// readMavenProject the java project
func analyzeJavaProject(path string) (*Project, error) {
var result []Project

entries, err := os.ReadDir(path)

if err != nil {
return nil, fmt.Errorf("error reading directory: %w", err)
}

for _, entry := range entries {
if "pom.xml" == entry.Name() {
Comment thread
saragluna marked this conversation as resolved.
Outdated
mavenProjects, err := analyzeMavenProject(path)
if err != nil {
return nil, fmt.Errorf("error analyzing maven project: %w", err)
}

for _, mavenProject := range mavenProjects {
javaProject := &javaProject{
mavenProject: mavenProject,
// todo (xiada) we need to add spring related analysis here
Comment thread
saragluna marked this conversation as resolved.
Outdated
}
project, err := detectDependencies(javaProject, &Project{
Language: Java,
Path: path,
DetectionRule: "Inferred by presence of: " + entry.Name(),
})
if err != nil {
return nil, fmt.Errorf("error applying rules: %w", err)
}
result = append(result, *project)

}
}
}

// todo (xiada) we should support multiple modules
return &result[0], nil
Comment thread
saragluna marked this conversation as resolved.
Outdated
}

// 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 analyzeMavenProject(projectPath string) ([]mavenProject, error) {
rootProject, _ := readMavenProject(filepath.Join(projectPath, "pom.xml"))
Comment thread
saragluna marked this conversation as resolved.
Outdated
var result []mavenProject

// if it has submodules
if len(rootProject.Modules) > 0 {
for _, m := range rootProject.Modules {
subModule, _ := readMavenProject(filepath.Join(projectPath, m, "pom.xml"))
result = append(result, *subModule)
}
} else {
result = append(result, *rootProject)
}
return result, nil
}

func readMavenProject(filePath string) (*mavenProject, error) {
bytes, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("error reading file: %w", err)
Comment thread
saragluna marked this conversation as resolved.
Outdated
}

var project mavenProject
if err := xml.Unmarshal(bytes, &project); err != nil {
return nil, fmt.Errorf("error parsing XML: %w", err)
Comment thread
saragluna marked this conversation as resolved.
Outdated
}

project.path = filepath.Dir(filePath)

return &project, nil
}

func detectDependencies(javaProject *javaProject, project *Project) (*Project, error) {
databaseDepMap := map[DatabaseDep]struct{}{}
if javaProject.mavenProject.Dependencies != nil {
Comment thread
weikanglim marked this conversation as resolved.
Outdated
for _, dep := range javaProject.mavenProject.Dependencies {
if dep.GroupId == "com.mysql" && dep.ArtifactId == "mysql-connector-j" {
databaseDepMap[DbMySql] = struct{}{}
}

if dep.GroupId == "org.postgresql" && dep.ArtifactId == "postgresql" {
databaseDepMap[DbPostgres] = struct{}{}
}
Comment thread
saragluna marked this conversation as resolved.
Outdated

}
}

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
}