@@ -4,20 +4,21 @@ package maven
44import (
55 "context"
66 "encoding/xml"
7+ "errors"
78 "fmt"
89 "net/url"
910 "strings"
1011 "time"
1112
13+ "github.com/git-pkgs/pom"
1214 "github.com/git-pkgs/registries/internal/core"
1315 "github.com/git-pkgs/registries/internal/urlparser"
1416)
1517
1618const (
17- DefaultURL = "https://repo1.maven.org/maven2"
18- SearchURL = "https://search.maven.org"
19- ecosystem = "maven"
20- maxParentDepth = 5
19+ DefaultURL = "https://repo1.maven.org/maven2"
20+ SearchURL = "https://search.maven.org"
21+ ecosystem = "maven"
2122 // minCoordParts is the minimum number of parts in a Maven coordinate (group:artifact)
2223 minCoordParts = 2
2324 // coordPartsWithVersion is the number of parts when version is included (group:artifact:version)
@@ -34,6 +35,7 @@ type Registry struct {
3435 baseURL string
3536 searchURL string
3637 client * core.Client
38+ resolver * pom.Resolver
3739 urls * URLs
3840}
3941
@@ -47,9 +49,30 @@ func New(baseURL string, client *core.Client) *Registry {
4749 client : client ,
4850 }
4951 r .urls = & URLs {baseURL : r .baseURL }
52+ r .resolver = pom .NewResolver (pom .NewCachingFetcher (& clientFetcher {client : client , baseURL : r .baseURL }))
5053 return r
5154}
5255
56+ // clientFetcher adapts core.Client to pom.Fetcher so the resolver's HTTP
57+ // goes through the same rate limiting, retries and circuit breaker as
58+ // every other registry call.
59+ type clientFetcher struct {
60+ client * core.Client
61+ baseURL string
62+ }
63+
64+ func (f * clientFetcher ) Fetch (ctx context.Context , gav pom.GAV ) (* pom.POM , error ) {
65+ body , err := f .client .GetBody (ctx , pom .POMURL (f .baseURL , gav ))
66+ if err != nil {
67+ return nil , err
68+ }
69+ return pom .ParsePOM (body )
70+ }
71+
72+ func (r * Registry ) effectivePOM (ctx context.Context , groupID , artifactID , version string ) (* pom.EffectivePOM , error ) {
73+ return r .resolver .Resolve (ctx , pom.GAV {GroupID : groupID , ArtifactID : artifactID , Version : version }, pom.Options {})
74+ }
75+
5376func (r * Registry ) Ecosystem () string {
5477 return ecosystem
5578}
@@ -69,58 +92,18 @@ type searchResponseBody struct {
6992}
7093
7194type searchDoc struct {
72- ID string `json:"id"`
73- GroupID string `json:"g"`
74- ArtifactID string `json:"a"`
75- Version string `json:"latestVersion"`
76- Timestamp int64 `json:"timestamp"`
77- VersionCount int `json:"versionCount"`
95+ ID string `json:"id"`
96+ GroupID string `json:"g"`
97+ ArtifactID string `json:"a"`
98+ Version string `json:"latestVersion"`
99+ Timestamp int64 `json:"timestamp"`
100+ VersionCount int `json:"versionCount"`
78101}
79102
80- // POM XML structures
81- type pomXML struct {
82- XMLName xml.Name `xml:"project"`
83- GroupID string `xml:"groupId"`
84- ArtifactID string `xml:"artifactId"`
85- Version string `xml:"version"`
86- Name string `xml:"name"`
87- Description string `xml:"description"`
88- URL string `xml:"url"`
89- Licenses []pomLicense `xml:"licenses>license"`
90- SCM pomSCM `xml:"scm"`
91- Parent * pomParent `xml:"parent"`
92- Dependencies []pomDep `xml:"dependencies>dependency"`
93- DependencyManagement struct {
94- Dependencies []pomDep `xml:"dependencies>dependency"`
95- } `xml:"dependencyManagement"`
103+ // pomDevelopers is the minimal XML shape needed for FetchMaintainers; the
104+ // pom library doesn't carry <developers>.
105+ type pomDevelopers struct {
96106 Developers []pomDeveloper `xml:"developers>developer"`
97- Properties map [string ]string
98- }
99-
100- type pomParent struct {
101- GroupID string `xml:"groupId"`
102- ArtifactID string `xml:"artifactId"`
103- Version string `xml:"version"`
104- }
105-
106- type pomLicense struct {
107- Name string `xml:"name"`
108- URL string `xml:"url"`
109- }
110-
111- type pomSCM struct {
112- URL string `xml:"url"`
113- Connection string `xml:"connection"`
114- DevConnection string `xml:"developerConnection"`
115- }
116-
117- type pomDep struct {
118- GroupID string `xml:"groupId"`
119- ArtifactID string `xml:"artifactId"`
120- Version string `xml:"version"`
121- Scope string `xml:"scope"`
122- Optional string `xml:"optional"`
123- Type string `xml:"type"`
124107}
125108
126109type pomDeveloper struct {
@@ -171,8 +154,8 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
171154 if err := r .client .GetJSON (ctx , searchURL , & searchResp ); err == nil && searchResp .Response .NumFound > 0 {
172155 doc := searchResp .Response .Docs [0 ]
173156 // Fetch the POM for more details
174- pom , _ := r .fetchPOM (ctx , groupID , artifactID , doc .Version , 0 )
175- return r .packageFromSearchAndPOM (doc , pom ), nil
157+ ep , _ := r .effectivePOM (ctx , groupID , artifactID , doc .Version )
158+ return r .packageFromSearchAndPOM (doc , ep ), nil
176159 }
177160
178161 // Fallback: try to get maven-metadata.xml
@@ -198,8 +181,8 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
198181 latestVersion = metadata .Versioning .Versions [len (metadata .Versioning .Versions )- 1 ]
199182 }
200183
201- pom , _ := r .fetchPOM (ctx , groupID , artifactID , latestVersion , 0 )
202- return r .packageFromMetadataAndPOM (metadata , pom ), nil
184+ ep , _ := r .effectivePOM (ctx , groupID , artifactID , latestVersion )
185+ return r .packageFromMetadataAndPOM (metadata , ep ), nil
203186}
204187
205188type mavenMetadata struct {
@@ -214,62 +197,7 @@ type versioning struct {
214197 Versions []string `xml:"versions>version"`
215198}
216199
217- func (r * Registry ) fetchPOM (ctx context.Context , groupID , artifactID , version string , depth int ) (* pomXML , error ) {
218- if depth > maxParentDepth {
219- return nil , fmt .Errorf ("max parent depth exceeded" )
220- }
221-
222- pomURL := fmt .Sprintf ("%s/%s/%s/%s/%s-%s.pom" ,
223- r .baseURL , groupIDToPath (groupID ), artifactID , version , artifactID , version )
224-
225- body , err := r .client .GetBody (ctx , pomURL )
226- if err != nil {
227- return nil , err
228- }
229-
230- var pom pomXML
231- if err := xml .Unmarshal (body , & pom ); err != nil {
232- return nil , err
233- }
234-
235- // Resolve parent POM if present
236- if pom .Parent != nil && depth < maxParentDepth {
237- parentPOM , err := r .fetchPOM (ctx , pom .Parent .GroupID , pom .Parent .ArtifactID , pom .Parent .Version , depth + 1 )
238- if err == nil {
239- mergePOMs (& pom , parentPOM )
240- }
241- }
242-
243- // Fill in groupID/version from parent if not set
244- if pom .GroupID == "" && pom .Parent != nil {
245- pom .GroupID = pom .Parent .GroupID
246- }
247- if pom .Version == "" && pom .Parent != nil {
248- pom .Version = pom .Parent .Version
249- }
250-
251- return & pom , nil
252- }
253-
254- func mergePOMs (child , parent * pomXML ) {
255- if child .Description == "" {
256- child .Description = parent .Description
257- }
258- if child .URL == "" {
259- child .URL = parent .URL
260- }
261- if len (child .Licenses ) == 0 {
262- child .Licenses = parent .Licenses
263- }
264- if child .SCM .URL == "" {
265- child .SCM = parent .SCM
266- }
267- if len (child .Developers ) == 0 {
268- child .Developers = parent .Developers
269- }
270- }
271-
272- func (r * Registry ) packageFromSearchAndPOM (doc searchDoc , pom * pomXML ) * core.Package {
200+ func (r * Registry ) packageFromSearchAndPOM (doc searchDoc , ep * pom.EffectivePOM ) * core.Package {
273201 pkg := & core.Package {
274202 Name : fmt .Sprintf ("%s:%s" , doc .GroupID , doc .ArtifactID ),
275203 Namespace : doc .GroupID ,
@@ -280,17 +208,11 @@ func (r *Registry) packageFromSearchAndPOM(doc searchDoc, pom *pomXML) *core.Pac
280208 },
281209 }
282210
283- if pom != nil {
284- pkg .Description = pom .Description
285- pkg .Homepage = pom .URL
286- pkg .Repository = extractRepository (pom )
287- pkg .Licenses = formatLicenses (pom .Licenses )
288- }
289-
211+ applyPOMMetadata (pkg , ep )
290212 return pkg
291213}
292214
293- func (r * Registry ) packageFromMetadataAndPOM (metadata mavenMetadata , pom * pomXML ) * core.Package {
215+ func (r * Registry ) packageFromMetadataAndPOM (metadata mavenMetadata , ep * pom. EffectivePOM ) * core.Package {
294216 pkg := & core.Package {
295217 Name : fmt .Sprintf ("%s:%s" , metadata .GroupID , metadata .ArtifactID ),
296218 Namespace : metadata .GroupID ,
@@ -300,26 +222,22 @@ func (r *Registry) packageFromMetadataAndPOM(metadata mavenMetadata, pom *pomXML
300222 },
301223 }
302224
303- if pom != nil {
304- pkg .Description = pom .Description
305- pkg .Homepage = pom .URL
306- pkg .Repository = extractRepository (pom )
307- pkg .Licenses = formatLicenses (pom .Licenses )
308- }
309-
225+ applyPOMMetadata (pkg , ep )
310226 return pkg
311227}
312228
313- func extractRepository (pom * pomXML ) string {
314- return urlparser .FirstRepoURL (pom .SCM .URL , pom .SCM .Connection )
315- }
316-
317- func formatLicenses (licenses []pomLicense ) string {
318- names := make ([]string , len (licenses ))
319- for i , l := range licenses {
229+ func applyPOMMetadata (pkg * core.Package , ep * pom.EffectivePOM ) {
230+ if ep == nil {
231+ return
232+ }
233+ pkg .Description = ep .Description
234+ pkg .Homepage = ep .URL
235+ pkg .Repository = urlparser .FirstRepoURL (ep .SCM .URL , ep .SCM .Connection )
236+ names := make ([]string , len (ep .Licenses ))
237+ for i , l := range ep .Licenses {
320238 names [i ] = l .Name
321239 }
322- return strings .Join (names , "," )
240+ pkg . Licenses = strings .Join (names , "," )
323241}
324242
325243func (r * Registry ) FetchVersions (ctx context.Context , name string ) ([]core.Version , error ) {
@@ -381,28 +299,26 @@ func (r *Registry) FetchDependencies(ctx context.Context, name, version string)
381299 return nil , fmt .Errorf ("invalid Maven coordinate: %s (expected groupId:artifactId)" , name )
382300 }
383301
384- pom , err := r .fetchPOM (ctx , groupID , artifactID , version , 0 )
302+ ep , err := r .effectivePOM (ctx , groupID , artifactID , version )
385303 if err != nil {
386- if httpErr , ok := err .(* core.HTTPError ); ok && httpErr .IsNotFound () {
304+ var httpErr * core.HTTPError
305+ if errors .As (err , & httpErr ) && httpErr .IsNotFound () {
387306 return nil , & core.NotFoundError {Ecosystem : ecosystem , Name : name , Version : version }
388307 }
389308 return nil , err
390309 }
391310
392- var deps []core.Dependency
393- for _ , d := range pom .Dependencies {
311+ deps := make ( []core.Dependency , 0 , len ( ep . Dependencies ))
312+ for _ , d := range ep .Dependencies {
394313 scope := mapMavenScope (d .Scope )
395- optional := d .Optional == "true"
396-
397- if optional {
314+ if d .Optional {
398315 scope = core .Optional
399316 }
400-
401317 deps = append (deps , core.Dependency {
402318 Name : fmt .Sprintf ("%s:%s" , d .GroupID , d .ArtifactID ),
403319 Requirements : d .Version ,
404320 Scope : scope ,
405- Optional : optional ,
321+ Optional : d . Optional ,
406322 })
407323 }
408324
@@ -440,13 +356,17 @@ func (r *Registry) FetchMaintainers(ctx context.Context, name string) ([]core.Ma
440356 }
441357
442358 latestVersion := versions [0 ].Number
443- pom , err := r .fetchPOM (ctx , groupID , artifactID , latestVersion , 0 )
359+ body , err := r .client . GetBody (ctx , pom . POMURL ( r . baseURL , pom. GAV { GroupID : groupID , ArtifactID : artifactID , Version : latestVersion }) )
444360 if err != nil {
445361 return nil , err
446362 }
363+ var devs pomDevelopers
364+ if err := xml .Unmarshal (body , & devs ); err != nil {
365+ return nil , err
366+ }
447367
448- maintainers := make ([]core.Maintainer , len (pom .Developers ))
449- for i , dev := range pom .Developers {
368+ maintainers := make ([]core.Maintainer , len (devs .Developers ))
369+ for i , dev := range devs .Developers {
450370 maintainers [i ] = core.Maintainer {
451371 UUID : dev .ID ,
452372 Login : dev .ID ,
0 commit comments