[ 
https://issues.apache.org/jira/browse/MINDEXER-195?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17746099#comment-17746099
 ] 

ASF GitHub Bot commented on MINDEXER-195:
-----------------------------------------

mbien commented on code in PR #323:
URL: https://github.com/apache/maven-indexer/pull/323#discussion_r1271468995


##########
search-backend-remoterepository/src/main/java/org/apache/maven/search/backend/remoterepository/internal/Java11HttpClientRemoteRepositorySearchTransport.java:
##########
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.search.backend.remoterepository.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchTransport;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Java 11 {@link HttpClient} backed transport.
+ */
+public class Java11HttpClientRemoteRepositorySearchTransport implements 
RemoteRepositorySearchTransport {
+    private final HttpClient client =
+            
HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NEVER).build();

Review Comment:
   consider adding a connection timeout to make this conference-wifi-proof, 
since I believe default has no dimeout:
   `.connectTimeout(Duration.ofSeconds(5))`



##########
search-backend-remoterepository/src/main/java/org/apache/maven/search/backend/remoterepository/internal/Java11HttpClientRemoteRepositorySearchTransport.java:
##########
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.search.backend.remoterepository.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchTransport;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Java 11 {@link HttpClient} backed transport.
+ */
+public class Java11HttpClientRemoteRepositorySearchTransport implements 
RemoteRepositorySearchTransport {
+    private final HttpClient client =
+            
HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NEVER).build();
+
+    private static class ResponseImpl implements Response {
+
+        private final HttpResponse<?> response;
+
+        private final InputStream inputStream;
+
+        private ResponseImpl(HttpResponse<?> response, InputStream 
inputStream) {
+            this.response = requireNonNull(response);
+            this.inputStream = inputStream;
+        }
+
+        @Override
+        public int getCode() {
+            return response.statusCode();
+        }
+
+        @Override
+        public Map<String, String> getHeaders() {
+            return response.headers().map().entrySet().stream()
+                    .map(e -> new AbstractMap.SimpleEntry<>(
+                            e.getKey(), e.getValue().get(0)))
+                    .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, 
AbstractMap.SimpleEntry::getValue));
+        }
+
+        @Override
+        public InputStream getBody() {
+            return inputStream;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (inputStream != null) {
+                inputStream.close();
+            }
+        }
+    }
+
+    @Override
+    public Response get(String serviceUri, Map<String, String> headers) throws 
IOException {
+        HttpRequest.Builder builder =
+                HttpRequest.newBuilder().uri(URI.create(serviceUri)).GET();

Review Comment:
   same for the actual request:
   `.timeout(Duration.ofSeconds(10))`



##########
search-backend-remoterepository/src/main/java/org/apache/maven/search/backend/remoterepository/internal/RemoteRepositorySearchBackendImpl.java:
##########
@@ -0,0 +1,388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.search.backend.remoterepository.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+
+import org.apache.maven.search.MAVEN;
+import org.apache.maven.search.Record;
+import org.apache.maven.search.SearchRequest;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchBackend;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchResponse;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchTransport;
+import org.apache.maven.search.request.Field;
+import org.apache.maven.search.support.SearchBackendSupport;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.parser.Parser;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Implementation of {@link RemoteRepositorySearchBackend} that is tested 
against Maven Central.
+ * All the methods are "loosely encapsulated" (are protected) to enable easy 
override of any
+ * required aspect of this implementation, to suit it against different remote 
repositories
+ * (HTML parsing) if needed.
+ */
+public class RemoteRepositorySearchBackendImpl extends SearchBackendSupport 
implements RemoteRepositorySearchBackend {
+    private final String baseUri;
+
+    private final RemoteRepositorySearchTransport transport;
+
+    private final Map<String, String> commonHeaders;
+
+    protected enum State {
+        G,
+        GA,
+        GAV,
+        GAVCE,
+        GAVCE1
+    }
+
+    /**
+     * Creates a customized instance of SMO backend, like an in-house 
instances of SMO or different IDs.
+     */
+    public RemoteRepositorySearchBackendImpl(
+            String backendId, String repositoryId, String baseUri, 
RemoteRepositorySearchTransport transport) {
+        super(backendId, repositoryId);
+        this.baseUri = requireNonNull(baseUri);
+        this.transport = requireNonNull(transport);
+
+        this.commonHeaders = new HashMap<>();
+        this.commonHeaders.put(
+                "User-Agent",
+                "Apache-Maven-Search-RR/" + discoverVersion() + " "
+                        + transport.getClass().getSimpleName());
+        this.commonHeaders.put("Accept", "application/json");
+    }
+
+    private String discoverVersion() {
+        Properties properties = new Properties();
+        InputStream inputStream = getClass()
+                .getClassLoader()
+                .getResourceAsStream(
+                        
"org/apache/maven/search/backend/smo/internal/remoterepository-version.properties");
+        if (inputStream != null) {
+            try (InputStream is = inputStream) {
+                properties.load(is);
+            } catch (IOException e) {
+                // fall through
+            }
+        }
+        return properties.getProperty("version", "unknown");
+    }
+
+    @Override
+    public String getBaseUri() {
+        return baseUri;
+    }
+
+    @Override
+    public RemoteRepositorySearchResponse search(SearchRequest searchRequest) 
throws IOException {
+        Context context = new Context(searchRequest);
+        String uri = baseUri;
+        State state = null;
+        if (context.getGroupId() != null) {

Review Comment:
   i really like this impl!



##########
search-backend-remoterepository/src/main/java/org/apache/maven/search/backend/remoterepository/internal/RemoteRepositorySearchBackendImpl.java:
##########
@@ -0,0 +1,388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.search.backend.remoterepository.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+
+import org.apache.maven.search.MAVEN;
+import org.apache.maven.search.Record;
+import org.apache.maven.search.SearchRequest;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchBackend;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchResponse;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchTransport;
+import org.apache.maven.search.request.Field;
+import org.apache.maven.search.support.SearchBackendSupport;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.parser.Parser;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Implementation of {@link RemoteRepositorySearchBackend} that is tested 
against Maven Central.
+ * All the methods are "loosely encapsulated" (are protected) to enable easy 
override of any
+ * required aspect of this implementation, to suit it against different remote 
repositories
+ * (HTML parsing) if needed.
+ */
+public class RemoteRepositorySearchBackendImpl extends SearchBackendSupport 
implements RemoteRepositorySearchBackend {
+    private final String baseUri;
+
+    private final RemoteRepositorySearchTransport transport;
+
+    private final Map<String, String> commonHeaders;
+
+    protected enum State {
+        G,
+        GA,
+        GAV,
+        GAVCE,
+        GAVCE1
+    }
+
+    /**
+     * Creates a customized instance of SMO backend, like an in-house 
instances of SMO or different IDs.
+     */
+    public RemoteRepositorySearchBackendImpl(
+            String backendId, String repositoryId, String baseUri, 
RemoteRepositorySearchTransport transport) {
+        super(backendId, repositoryId);
+        this.baseUri = requireNonNull(baseUri);
+        this.transport = requireNonNull(transport);
+
+        this.commonHeaders = new HashMap<>();
+        this.commonHeaders.put(
+                "User-Agent",
+                "Apache-Maven-Search-RR/" + discoverVersion() + " "
+                        + transport.getClass().getSimpleName());
+        this.commonHeaders.put("Accept", "application/json");
+    }
+
+    private String discoverVersion() {
+        Properties properties = new Properties();
+        InputStream inputStream = getClass()
+                .getClassLoader()
+                .getResourceAsStream(
+                        
"org/apache/maven/search/backend/smo/internal/remoterepository-version.properties");
+        if (inputStream != null) {
+            try (InputStream is = inputStream) {
+                properties.load(is);
+            } catch (IOException e) {
+                // fall through
+            }
+        }
+        return properties.getProperty("version", "unknown");
+    }
+
+    @Override
+    public String getBaseUri() {
+        return baseUri;
+    }
+
+    @Override
+    public RemoteRepositorySearchResponse search(SearchRequest searchRequest) 
throws IOException {
+        Context context = new Context(searchRequest);
+        String uri = baseUri;
+        State state = null;
+        if (context.getGroupId() != null) {
+            uri += context.getGroupId().replace('.', '/') + "/";
+            state = State.G;
+            if (context.getArtifactId() != null) {
+                uri += context.getArtifactId() + "/";
+                state = State.GA;
+                if (context.getVersion() == null) {
+                    uri += "maven-metadata.xml";
+                } else {
+                    uri += context.getVersion() + "/";
+                    state = State.GAV;
+                    if (context.getFileExtension() != null) {
+                        // we go for actually specified artifact
+                        uri += context.getArtifactId() + "-" + 
context.getVersion();
+                        if (context.getClassifier() != null) {
+                            uri += "-" + context.getClassifier();
+                        }
+                        uri += "." + context.getFileExtension();
+                        state = State.GAVCE;
+                        if (context.getSha1() != null) {
+                            state = State.GAVCE1;
+                        }
+                    }
+                }
+            }
+        }
+        if (state == null) {
+            throw new IllegalArgumentException("Unsupported Query: " + 
searchRequest.getQuery());
+        }
+
+        int totalHits = 0;
+        List<Record> page = new 
ArrayList<>(searchRequest.getPaging().getPageSize());
+        Document document = null;
+        if (state.ordinal() < State.GAVCE.ordinal()) {
+            Parser parser = state == State.GA ? Parser.xmlParser() : 
Parser.htmlParser();
+            try (RemoteRepositorySearchTransport.Response response = 
transport.get(uri, commonHeaders)) {
+                if (response.getCode() == 200) {
+                    document = Jsoup.parse(response.getBody(), 
StandardCharsets.UTF_8.name(), uri, parser);
+                }
+            }
+
+            if (document == null) {
+                return new RemoteRepositorySearchResponseImpl(searchRequest, 
0, Collections.emptyList(), uri, null);
+            }
+
+            switch (state) {
+                case G:
+                    totalHits = populateG(context, document, page);
+                    break;
+                case GA:
+                    totalHits = populateGA(context, document, page);
+                    break;
+                case GAV:
+                    totalHits = populateGAV(context, document, page);
+                    break;
+                default:
+                    throw new IllegalStateException("State" + state); // 
checkstyle
+            }
+        } else {
+            try (RemoteRepositorySearchTransport.Response response = 
transport.head(uri, commonHeaders)) {
+                if (response.getCode() == 200) {
+                    boolean matches = context.getSha1() == null;
+                    if (context.getSha1() != null) {
+                        try (RemoteRepositorySearchTransport.Response 
sha1Response =
+                                transport.get(uri + ".sha1", commonHeaders)) {
+                            if (response.getCode() == 200) {
+                                String remoteSha1 = 
readChecksum(sha1Response.getBody());
+                                matches = Objects.equals(context.getSha1(), 
remoteSha1);
+                            }
+                        }
+                    }
+                    if (matches) {
+                        page.add(create(
+                                context.getGroupId(),
+                                context.getArtifactId(),
+                                context.getVersion(),
+                                context.getClassifier(),
+                                context.getFileExtension()));
+                        totalHits = 1;
+                    }
+                }
+            }
+        }
+        return new RemoteRepositorySearchResponseImpl(searchRequest, 
totalHits, page, uri, document);
+    }
+
+    protected boolean isChecksum(String name) {
+        return name.endsWith(".sha1") || name.endsWith(".md5") || 
name.endsWith(".sha256") || name.endsWith(".sha512");
+    }
+
+    protected boolean isSignature(String name) {
+        return name.endsWith(".asc") || name.endsWith(".sigstore");
+    }
+
+    protected boolean isMetadata(String name) {
+        return name.equals("maven-metadata.xml");
+    }
+
+    /**
+     * Returns {@code true} if the name is not empty, not directory special 
(".."), is not metadata
+     * is not signature and is not checksum. Hence, it should be a name of 
interest.
+     */
+    protected boolean accept(String name) {
+        return !name.isEmpty() && !name.contains("..") && !isMetadata(name) && 
!isSignature(name) && !isChecksum(name);
+    }
+
+    /**
+     * Extracts the "name" from {@code href} attribute. In case of Maven 
Central, the href
+     * attribute contains name in form of {@code "name/"} (followed by slash), 
if name denotes
+     * a directory. The trailing slash is removed by this method, if any.
+     * <p>
+     * Override this method if needed (parsing different HTML output than 
Maven Central).
+     */
+    protected String nameInHref(Element element) {
+        String name = element.attr("href");
+        if (name.endsWith("/")) {
+            name = name.substring(0, name.length() - 1);
+        }
+        return name;
+    }
+
+    /**
+     * Method parsing document out of HTML page like this one:
+     * <a 
href="https://repo.maven.apache.org/maven2/org/apache/maven/indexer/";>https://repo.maven.apache.org/maven2/org/apache/maven/indexer/</a>
+     * <p>
+     * Note: this method is "best effort" and may enlist non-existent As 
(think nested Gs).
+     * <p>
+     * Override this method if needed (parsing different HTML output than 
Maven Central).
+     */
+    protected int populateG(Context context, Document document, List<Record> 
page) {
+        // Index HTML page like this one:
+        // https://repo.maven.apache.org/maven2/org/apache/maven/indexer/
+        Element contents = document.getElementById("contents");
+        if (contents != null) {
+            for (Element element : contents.getElementsByTag("a")) {
+                String name = nameInHref(element);
+                if (accept(name)) {
+                    page.add(create(context.getGroupId(), name, null, null, 
null));
+                }
+            }
+        }
+        return page.size();
+    }
+
+    /**
+     * Method parsing document out of XML Maven Metadata like this one:
+     * <a 
href="https://repo.maven.apache.org/maven2/org/apache/maven/indexer/search-api/maven-metadata.xml";>https://repo.maven.apache.org/maven2/org/apache/maven/indexer/search-api/maven-metadata.xml</a>
+     */
+    protected int populateGA(Context context, Document document, List<Record> 
page) {
+        // Maven Metadata XML like this one:
+        // 
https://repo.maven.apache.org/maven2/org/apache/maven/indexer/search-api/maven-metadata.xml
+        Element metadata = document.getElementsByTag("metadata").first();
+        if (metadata != null) {
+            Element versioning = 
metadata.getElementsByTag("versioning").first();
+            if (versioning != null) {
+                Element versions = 
versioning.getElementsByTag("versions").first();
+                if (versions != null) {
+                    for (Element version : 
versions.getElementsByTag("version")) {
+                        page.add(create(context.getGroupId(), 
context.getArtifactId(), version.text(), null, null));
+                    }
+                }
+            }
+        }
+        return page.size();
+    }
+
+    /**
+     * Method parsing document out of HTML page like this one:
+     * <a 
href="https://repo.maven.apache.org/maven2/org/apache/maven/indexer/search-api/7.0.3/";>https://repo.maven.apache.org/maven2/org/apache/maven/indexer/search-api/7.0.3/</a>
+     * <p>
+     * Note: this method is "best effort" and may enlist fake artifacts.
+     * <p>
+     * Override this method if needed (parsing different HTML output than 
Maven Central).
+     */
+    protected int populateGAV(Context context, Document document, List<Record> 
page) {
+        // Index HTML page like this one:
+        // 
https://repo.maven.apache.org/maven2/org/apache/maven/indexer/search-api/7.0.3/
+        Element contents = document.getElementById("contents");
+        if (contents != null) {
+            for (Element element : contents.getElementsByTag("a")) {
+                // skip possible subdirectories and files without extensions
+                String name = element.attr("href");
+                if (name.endsWith("/") || !name.contains(".")) {
+                    continue;
+                }
+                name = nameInHref(element);
+                if (accept(name)) {
+                    if (name.startsWith(context.getArtifactId())) {
+                        name = name.substring(context.getArtifactId().length() 
+ 1);
+                        if (name.startsWith(context.getVersion())) {
+                            name = 
name.substring(context.getVersion().length() + 1);
+                            String ext = null;
+                            String classifier = null;
+                            if (name.contains(".")) {
+                                while (name.contains(".")) {
+                                    if (ext == null) {
+                                        ext = 
name.substring(name.lastIndexOf('.') + 1);
+                                    } else {
+                                        ext = 
name.substring(name.lastIndexOf('.') + 1) + "." + ext;
+                                    }
+                                    name = name.substring(0, 
name.lastIndexOf('.'));
+                                }
+                                classifier = name.isEmpty() ? null : name;
+                            } else {
+                                ext = name;
+                            }
+                            page.add(create(
+                                    context.getGroupId(),
+                                    context.getArtifactId(),
+                                    context.getVersion(),
+                                    classifier,
+                                    ext));
+                        }
+                    }
+                }
+            }
+        }
+        return page.size();
+    }
+
+    /**
+     * Creates a {@link Record} instance using passed in field values. All 
field values except
+     * {@code groupId} are optional (nullable).
+     */
+    protected Record create(
+            String groupId, String artifactId, String version, String 
classifier, String fileExtension) {
+        HashMap<Field, Object> result = new HashMap<>();
+
+        mayPut(result, MAVEN.GROUP_ID, groupId);
+        mayPut(result, MAVEN.ARTIFACT_ID, artifactId);
+        mayPut(result, MAVEN.VERSION, version);
+        mayPut(result, MAVEN.CLASSIFIER, classifier);
+        mayPut(result, MAVEN.FILE_EXTENSION, fileExtension);
+        return new Record(getBackendId(), getRepositoryId(), null, null, 
result);
+    }
+
+    private static String readChecksum(InputStream inputStream) throws 
IOException {
+        String checksum = "";
+        try (BufferedReader br = new BufferedReader(new 
InputStreamReader(inputStream, StandardCharsets.UTF_8), 512)) {
+            while (true) {
+                String line = br.readLine();
+                if (line == null) {
+                    break;
+                }
+                line = line.trim();
+                if (line.length() > 0) {
+                    checksum = line;
+                    break;
+                }
+            }
+        }
+
+        if (checksum.matches(".+= [0-9A-Fa-f]+")) {
+            int lastSpacePos = checksum.lastIndexOf(' ');
+            checksum = checksum.substring(lastSpacePos + 1);
+        } else {
+            int spacePos = checksum.indexOf(' ');
+
+            if (spacePos != -1) {
+                checksum = checksum.substring(0, spacePos);
+            }
+        }
+
+        return checksum;
+    }
+
+    private static void mayPut(Map<Field, Object> result, Field fieldName, 
Object value) {
+        if (value == null) {
+            return;
+        }
+        if (value instanceof String && ((String) value).trim().isEmpty()) {

Review Comment:
   super nitpick:
   `.trim().isEmpty()` -> `.isBlank()`
   
   I only noticed since I have a small collection of refactoring rules here: 
https://github.com/mbien/jackpot-inspections which fired on this.



##########
search-backend-remoterepository/src/main/java/org/apache/maven/search/backend/remoterepository/internal/Java11HttpClientRemoteRepositorySearchTransport.java:
##########
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.search.backend.remoterepository.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchTransport;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Java 11 {@link HttpClient} backed transport.
+ */
+public class Java11HttpClientRemoteRepositorySearchTransport implements 
RemoteRepositorySearchTransport {
+    private final HttpClient client =
+            
HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NEVER).build();
+
+    private static class ResponseImpl implements Response {
+
+        private final HttpResponse<?> response;
+
+        private final InputStream inputStream;
+
+        private ResponseImpl(HttpResponse<?> response, InputStream 
inputStream) {
+            this.response = requireNonNull(response);
+            this.inputStream = inputStream;
+        }
+
+        @Override
+        public int getCode() {
+            return response.statusCode();
+        }
+
+        @Override
+        public Map<String, String> getHeaders() {
+            return response.headers().map().entrySet().stream()
+                    .map(e -> new AbstractMap.SimpleEntry<>(
+                            e.getKey(), e.getValue().get(0)))
+                    .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, 
AbstractMap.SimpleEntry::getValue));
+        }
+
+        @Override
+        public InputStream getBody() {
+            return inputStream;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (inputStream != null) {
+                inputStream.close();
+            }
+        }
+    }
+
+    @Override
+    public Response get(String serviceUri, Map<String, String> headers) throws 
IOException {
+        HttpRequest.Builder builder =
+                HttpRequest.newBuilder().uri(URI.create(serviceUri)).GET();
+        for (Map.Entry<String, String> header : headers.entrySet()) {
+            builder.header(header.getKey(), header.getValue());
+        }
+        HttpRequest request = builder.build();
+        try {
+            HttpResponse<InputStream> response = client.send(request, 
HttpResponse.BodyHandlers.ofInputStream());
+            return new ResponseImpl(response, response.body());
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IOException(e);
+        }
+    }
+
+    @Override
+    public Response head(String serviceUri, Map<String, String> headers) 
throws IOException {
+        HttpRequest.Builder builder = HttpRequest.newBuilder()
+                .uri(URI.create(serviceUri))
+                .method("HEAD", HttpRequest.BodyPublishers.noBody());

Review Comment:
   same here:
   `.timeout(Duration.ofSeconds(10))`



##########
search-backend-remoterepository/src/main/java/org/apache/maven/search/backend/remoterepository/internal/RemoteRepositorySearchBackendImpl.java:
##########
@@ -0,0 +1,388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.search.backend.remoterepository.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+
+import org.apache.maven.search.MAVEN;
+import org.apache.maven.search.Record;
+import org.apache.maven.search.SearchRequest;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchBackend;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchResponse;
+import 
org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchTransport;
+import org.apache.maven.search.request.Field;
+import org.apache.maven.search.support.SearchBackendSupport;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.parser.Parser;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Implementation of {@link RemoteRepositorySearchBackend} that is tested 
against Maven Central.
+ * All the methods are "loosely encapsulated" (are protected) to enable easy 
override of any
+ * required aspect of this implementation, to suit it against different remote 
repositories
+ * (HTML parsing) if needed.
+ */
+public class RemoteRepositorySearchBackendImpl extends SearchBackendSupport 
implements RemoteRepositorySearchBackend {
+    private final String baseUri;
+
+    private final RemoteRepositorySearchTransport transport;
+
+    private final Map<String, String> commonHeaders;
+
+    protected enum State {
+        G,
+        GA,
+        GAV,
+        GAVCE,
+        GAVCE1
+    }
+
+    /**
+     * Creates a customized instance of SMO backend, like an in-house 
instances of SMO or different IDs.
+     */
+    public RemoteRepositorySearchBackendImpl(
+            String backendId, String repositoryId, String baseUri, 
RemoteRepositorySearchTransport transport) {
+        super(backendId, repositoryId);
+        this.baseUri = requireNonNull(baseUri);
+        this.transport = requireNonNull(transport);
+
+        this.commonHeaders = new HashMap<>();
+        this.commonHeaders.put(
+                "User-Agent",
+                "Apache-Maven-Search-RR/" + discoverVersion() + " "
+                        + transport.getClass().getSimpleName());
+        this.commonHeaders.put("Accept", "application/json");

Review Comment:
   nitpick: could be immutable `Map.of(entry, entry)` if you want





> New Search API backend: remoterepository
> ----------------------------------------
>
>                 Key: MINDEXER-195
>                 URL: https://issues.apache.org/jira/browse/MINDEXER-195
>             Project: Maven Indexer
>          Issue Type: Improvement
>            Reporter: Tamas Cservenak
>            Assignee: Tamas Cservenak
>            Priority: Major
>             Fix For: 7.0.4
>
>
> A limited backend (not really a general query backend) that still can answer 
> some basic queries and works agains a remote repository.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to