Skip to content

Commit f66d5ea

Browse files
committed
HSEARCH-5481 Try using Central Search api in DependencyManagementIncludesAllGroupIdArtifactsRule
1 parent 94ab6d6 commit f66d5ea

File tree

1 file changed

+154
-29
lines changed

1 file changed

+154
-29
lines changed

build/enforcer/src/main/java/org/hibernate/search/build/enforcer/DependencyManagementIncludesAllGroupIdArtifactsRule.java

Lines changed: 154 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import java.io.InputStreamReader;
1313
import java.net.URI;
1414
import java.net.URLEncoder;
15+
import java.net.http.HttpClient;
16+
import java.net.http.HttpRequest;
17+
import java.net.http.HttpResponse;
1518
import java.nio.charset.StandardCharsets;
1619
import java.util.ArrayList;
1720
import java.util.HashSet;
@@ -45,6 +48,7 @@ public class DependencyManagementIncludesAllGroupIdArtifactsRule extends Abstrac
4548
*/
4649
private static final String BASE_URL_FORMAT =
4750
"https://search.maven.org/solrsearch/select?q=%s&core=gav&start=%d&rows=%d&wt=json";
51+
private static final String CENTRAL_SEARCH_URL = "https://central.sonatype.com/api/internal/browse/components";
4852
private static final String MAVEN_STATUS_URL = "https://status.maven.org/api/v2/summary.json";
4953
private static final int ROWS_PER_PAGE = 100;
5054
private static final int MAX_RETRIES = 5;
@@ -71,6 +75,8 @@ public class DependencyManagementIncludesAllGroupIdArtifactsRule extends Abstrac
7175
*/
7276
private Set<Dependency> dependenciesToSkip = new HashSet<>();
7377

78+
private HttpClient client = HttpClient.newBuilder().build();
79+
7480
public void execute() throws EnforcerRuleException {
7581
Set<String> dependencies = session.getCurrentProject()
7682
.getDependencyManagement()
@@ -123,27 +129,7 @@ public void execute() throws EnforcerRuleException {
123129

124130
if ( mavenStatus.isSearchAvailable() ) {
125131
for ( Artifact filter : toQuery ) {
126-
StringBuilder queryBuilder = new StringBuilder();
127-
queryBuilder.append( "g:" ).append( encodeValue( filter.g ) );
128-
129-
if ( filter.a != null && !filter.a.trim().isEmpty() ) {
130-
queryBuilder.append( "+AND+a:" ).append( encodeValue( filter.a ) );
131-
}
132-
if ( filter.v != null && !filter.v.trim().isEmpty() ) {
133-
queryBuilder.append( "+AND+v:" ).append( encodeValue( filter.v ) );
134-
}
135-
136-
int start = 0;
137-
do {
138-
String url = String.format( Locale.ROOT, BASE_URL_FORMAT, queryBuilder, start, ROWS_PER_PAGE );
139-
SearchResponse response = fetch( gson, url, SearchResponse.class, SearchResponse::empty );
140-
foundArtifacts.addAll( response.response.docs );
141-
if ( response.response.start + response.response.docs.size() >= response.response.numFound ) {
142-
break;
143-
}
144-
start += ROWS_PER_PAGE;
145-
}
146-
while ( true );
132+
mavenCentralSearch( filter, gson, foundArtifacts );
147133
}
148134
}
149135
else {
@@ -167,6 +153,45 @@ public void execute() throws EnforcerRuleException {
167153
}
168154
}
169155

156+
private void mavenCentralSearch(Artifact filter, Gson gson, Set<Artifact> foundArtifacts) throws EnforcerRuleException {
157+
CentralSearchRequest request = new CentralSearchRequest( filter );
158+
getLog().info( "Fetching information for " + request );
159+
do {
160+
CentralSearchResponse response =
161+
post( gson, client, CENTRAL_SEARCH_URL, request, CentralSearchResponse.class,
162+
CentralSearchResponse::empty );
163+
foundArtifacts.addAll( response.components.stream().map( Artifact::new ).toList() );
164+
if ( request.nextPage() >= response.pageCount ) {
165+
break;
166+
}
167+
}
168+
while ( true );
169+
}
170+
171+
private void legacyMavenSolrSearch(Artifact filter, Gson gson, Set<Artifact> foundArtifacts) throws EnforcerRuleException {
172+
StringBuilder queryBuilder = new StringBuilder();
173+
queryBuilder.append( "g:" ).append( encodeValue( filter.g ) );
174+
175+
if ( filter.a != null && !filter.a.trim().isEmpty() ) {
176+
queryBuilder.append( "+AND+a:" ).append( encodeValue( filter.a ) );
177+
}
178+
if ( filter.v != null && !filter.v.trim().isEmpty() ) {
179+
queryBuilder.append( "+AND+v:" ).append( encodeValue( filter.v ) );
180+
}
181+
182+
int start = 0;
183+
do {
184+
String url = String.format( Locale.ROOT, BASE_URL_FORMAT, queryBuilder, start, ROWS_PER_PAGE );
185+
SearchResponse response = fetch( gson, url, SearchResponse.class, SearchResponse::empty );
186+
foundArtifacts.addAll( response.response.docs );
187+
if ( response.response.start + response.response.docs.size() >= response.response.numFound ) {
188+
break;
189+
}
190+
start += ROWS_PER_PAGE;
191+
}
192+
while ( true );
193+
}
194+
170195
private static String dependencyString(Dependency d) {
171196
return String.format( Locale.ROOT, GAV_FORMAT, d.getGroupId(), d.getArtifactId(), d.getVersion() );
172197
}
@@ -181,16 +206,45 @@ private static boolean shouldSkip(String gav, Set<Pattern> skipPatterns) {
181206
}
182207

183208
private <T> T fetch(Gson gson, String url, Class<T> klass, Supplier<T> empty) throws EnforcerRuleException {
209+
return withRetry(
210+
() -> {
211+
try (
212+
var stream = URI.create( url ).toURL().openStream(); var isr = new InputStreamReader( stream );
213+
var reader = new JsonReader( isr )
214+
) {
215+
getLog().info( "Fetching from " + url );
216+
return gson.fromJson( reader, klass );
217+
}
218+
}, empty
219+
);
220+
}
221+
222+
private <T> T post(Gson gson, HttpClient client, String url, CentralSearchRequest searchRequest, Class<T> klass,
223+
Supplier<T> empty)
224+
throws EnforcerRuleException {
225+
return withRetry(
226+
() -> {
227+
HttpRequest request = HttpRequest.newBuilder()
228+
.uri( URI.create( url ) )
229+
.header( "Content-Type", "application/json" )
230+
.POST( HttpRequest.BodyPublishers.ofString( gson.toJson( searchRequest ) ) )
231+
.build();
232+
233+
HttpResponse<String> response = client.send( request, HttpResponse.BodyHandlers.ofString() );
234+
235+
return gson.fromJson( response.body(), klass );
236+
}, empty
237+
);
238+
}
239+
240+
private <T> T withRetry(ThrowingSupplier<T> action, Supplier<T> empty) throws EnforcerRuleException {
184241
for ( int i = 0; i < MAX_RETRIES; i++ ) {
185-
try (
186-
var stream = URI.create( url ).toURL().openStream(); var isr = new InputStreamReader( stream );
187-
var reader = new JsonReader( isr )
188-
) {
189-
getLog().info( "Fetching from " + url );
190-
return gson.fromJson( reader, klass );
242+
try {
243+
return action.get();
244+
191245
}
192246
catch (IOException e) {
193-
getLog().warn( "Fetching from " + url + " failed. Retrying in " + RETRY_DELAY_SECONDS
247+
getLog().warn( "Fetching failed. Retrying in " + RETRY_DELAY_SECONDS
194248
+ "s... (Attempt " + ( i + 1 ) + "/" + ( MAX_RETRIES ) + "): " + e.getMessage() );
195249
try {
196250
TimeUnit.SECONDS.sleep( RETRY_DELAY_SECONDS );
@@ -200,8 +254,12 @@ private <T> T fetch(Gson gson, String url, Class<T> klass, Supplier<T> empty) th
200254
throw new EnforcerRuleException( ie );
201255
}
202256
}
257+
catch (InterruptedException ie) {
258+
Thread.currentThread().interrupt();
259+
throw new EnforcerRuleException( ie );
260+
}
203261
}
204-
getLog().warn( "Fetching from " + url + " failed after " + ( MAX_RETRIES ) + " attempts." );
262+
getLog().warn( "Fetching from failed after " + ( MAX_RETRIES ) + " attempts." );
205263
return empty.get();
206264
}
207265

@@ -223,6 +281,12 @@ public Artifact(String g, String a, String v) {
223281
public Artifact() {
224282
}
225283

284+
public Artifact(CentralComponent centralComponent) {
285+
this.g = centralComponent.namespace;
286+
this.a = centralComponent.name;
287+
this.v = centralComponent.version;
288+
}
289+
226290
public String formattedString() {
227291
return String.format( Locale.ROOT, GAV_FORMAT, g, a, v );
228292
}
@@ -320,4 +384,65 @@ static StatusSummary empty() {
320384
return statusSummary;
321385
}
322386
}
387+
388+
// Central search API DTOs:
389+
static class CentralSearchRequest {
390+
public int page;
391+
public int size;
392+
public String searchTerm;
393+
public List<Object> filter;
394+
395+
CentralSearchRequest(Artifact filter) {
396+
this.page = 0;
397+
this.size = 20;
398+
this.filter = null;
399+
StringBuilder queryBuilder = new StringBuilder();
400+
queryBuilder.append( "namespace:" ).append( filter.g );
401+
402+
if ( filter.a != null && !filter.a.trim().isEmpty() ) {
403+
queryBuilder.append( " name:" ).append( filter.a );
404+
}
405+
if ( filter.v != null && !filter.v.trim().isEmpty() ) {
406+
queryBuilder.append( " version:" ).append( filter.v );
407+
}
408+
409+
this.searchTerm = queryBuilder.toString();
410+
}
411+
412+
int nextPage() {
413+
return this.page++;
414+
}
415+
416+
@Override
417+
public String toString() {
418+
return searchTerm;
419+
}
420+
}
421+
422+
static class CentralSearchResponse {
423+
public List<CentralComponent> components;
424+
public int page;
425+
public int pageSize;
426+
public int pageCount;
427+
public int totalResultCount;
428+
public int totalCount;
429+
430+
static CentralSearchResponse empty() {
431+
CentralSearchResponse res = new CentralSearchResponse();
432+
res.components = List.of();
433+
return res;
434+
}
435+
}
436+
437+
static class CentralComponent {
438+
public String id;
439+
public String namespace;
440+
public String name;
441+
public String version;
442+
}
443+
444+
private interface ThrowingSupplier<T> {
445+
446+
T get() throws IOException, InterruptedException;
447+
}
323448
}

0 commit comments

Comments
 (0)