diff --git a/cobigen/cobigen-core-api/pom.xml b/cobigen/cobigen-core-api/pom.xml
index 7f8555d058..380b6e4345 100644
--- a/cobigen/cobigen-core-api/pom.xml
+++ b/cobigen/cobigen-core-api/pom.xml
@@ -27,7 +27,7 @@
com.google.guava
guava
-
+
org.zeroturnaround
@@ -40,11 +40,32 @@
core-test
test
+
commons-io
commons-io
test
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+
+
+
+
+ com.github.tomakehurst
+ wiremock-standalone
+ 2.27.2
+ test
+
diff --git a/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/MavenSearchRepositoryConstants.java b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/MavenSearchRepositoryConstants.java
new file mode 100644
index 0000000000..5fa96b47ad
--- /dev/null
+++ b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/MavenSearchRepositoryConstants.java
@@ -0,0 +1,58 @@
+package com.devonfw.cobigen.api.constants;
+
+/**
+ * Constants needed for handling the maven search REST APIs
+ */
+public class MavenSearchRepositoryConstants {
+
+ /**
+ * Maven repository URL
+ */
+ public static String MAVEN_REPOSITORY_URL = "https://search.maven.org";
+
+ /**
+ * Maven repository download link
+ */
+ public static String MAVEN_REPOSITORY_DOWNLOAD_LINK = "https://repo1.maven.org/maven2";
+
+ /**
+ * Maven target link
+ */
+ public static String MAVEN_TARGET_LINK = "solrsearch/select";
+
+ /**
+ * Nexus2 repository URL
+ */
+ public static String NEXUS2_REPOSITORY_URL = "https://s01.oss.sonatype.org";
+
+ /**
+ * Nexus2 repository link
+ */
+ public static String NEXUS2_REPOSITORY_LINK = "service/local/repositories/releases/content";
+
+ /**
+ * Nexus2 target link
+ */
+ public static String NEXUS2_TARGET_LINK = "service/local/lucene/search";
+
+ /**
+ * Nexus3 target link
+ */
+ public static String NEXUS3_TARGET_LINK = "service/rest/v1/search";
+
+ /**
+ * Nexus3 repository URL
+ */
+ public static String NEXUS3_REPOSITORY_URL = "";
+
+ /**
+ * Jfrog repository URL
+ */
+ public static String JFROG_REPOSITORY_URL = "http://localhost:8082/artifactory";
+
+ /**
+ * Jfrog target link
+ */
+ public static String JFROG_TARGET_LINK = "artifactory/api/search/gavc";
+
+}
diff --git a/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/MavenSearchRepositoryType.java b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/MavenSearchRepositoryType.java
new file mode 100644
index 0000000000..e5c8733d5f
--- /dev/null
+++ b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/MavenSearchRepositoryType.java
@@ -0,0 +1,28 @@
+package com.devonfw.cobigen.api.constants;
+
+/**
+ * Maven search repository types used to identify and name the available search REST API types (add new search
+ * repository types/versions here)
+ */
+public enum MavenSearchRepositoryType {
+
+ /**
+ * Nexus2 search repository type
+ */
+ NEXUS2,
+
+ /**
+ * Nexus3 search repository type
+ */
+ NEXUS3,
+
+ /**
+ * Maven search repository type
+ */
+ MAVEN,
+
+ /**
+ * Jfrog search repository type
+ */
+ JFROG
+}
diff --git a/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/exception/RestSearchResponseException.java b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/exception/RestSearchResponseException.java
new file mode 100644
index 0000000000..e4a1b6e208
--- /dev/null
+++ b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/exception/RestSearchResponseException.java
@@ -0,0 +1,40 @@
+package com.devonfw.cobigen.api.exception;
+
+/** Exception to indicate that the REST search API encountered a problem while accessing the server. */
+public class RestSearchResponseException extends CobiGenRuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new {@link RestSearchResponseException} with the given message
+ *
+ * @param message error message of the exception
+ */
+ public RestSearchResponseException(String message) {
+
+ super(message);
+ }
+
+ /**
+ * Creates a new {@link RestSearchResponseException} with the specified message and the causing {@link Throwable}
+ *
+ * @param message describing the exception
+ * @param cause the causing Throwable
+ */
+ public RestSearchResponseException(String message, Throwable cause) {
+
+ super(message, cause);
+ }
+
+ /**
+ * Creates a new {@link RestSearchResponseException} with the specified message and the causing {@link Throwable}
+ *
+ * @param message describing the exception
+ * @param statusCode status code causing the {@link RestSearchResponseException} or null if not available
+ */
+ public RestSearchResponseException(String message, String statusCode) {
+
+ super(message + statusCode);
+ }
+
+}
diff --git a/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/MavenUtil.java b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/MavenUtil.java
index a50d684117..69f96c4e50 100644
--- a/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/MavenUtil.java
+++ b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/MavenUtil.java
@@ -27,6 +27,9 @@
import com.devonfw.cobigen.api.constants.MavenConstants;
import com.devonfw.cobigen.api.exception.CobiGenRuntimeException;
+import com.devonfw.cobigen.api.exception.RestSearchResponseException;
+import com.devonfw.cobigen.api.util.to.SearchResponseFactory;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
@@ -340,4 +343,27 @@ public static Path getProjectRoot(Path inputFile, boolean topLevel) {
LOG.debug("Project root could not be found.");
return null;
}
+
+ /**
+ * Retrieves a list of download URLs by groupId from the specified repository search REST API using authentication
+ * with bearer token
+ *
+ * @param baseUrl String of the repository server URL
+ * @param groupId the groupId to search for
+ * @param authToken bearer token to use for authentication
+ * @return List of artifact download URLS or null if an error occurred
+ */
+ public static List retrieveMavenArtifactsByGroupId(String baseUrl, String groupId, String authToken) {
+
+ try {
+
+ return SearchResponseFactory.searchArtifactDownloadLinks(baseUrl, groupId, authToken);
+ } catch (RestSearchResponseException | JsonProcessingException | MalformedURLException e) {
+ LOG.error("Unable to get artifacts from {} by groupId {}", baseUrl, groupId, e);
+ // TODO: Handle Eclipse, CLI and MavenPlugin here (f.e. with a new Exception)
+ return null;
+ }
+
+ }
+
}
diff --git a/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/to/AbstractSearchResponse.java b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/to/AbstractSearchResponse.java
new file mode 100644
index 0000000000..eb00dba93a
--- /dev/null
+++ b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/to/AbstractSearchResponse.java
@@ -0,0 +1,164 @@
+package com.devonfw.cobigen.api.util.to;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.devonfw.cobigen.api.constants.MavenSearchRepositoryType;
+import com.devonfw.cobigen.api.exception.RestSearchResponseException;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * This interface should be inherited for all maven REST search API responses to properly convert {@link JsonProperty}
+ * from responses to valid download URLs
+ */
+public abstract class AbstractSearchResponse {
+
+ /** Logger instance. */
+ @JsonIgnore
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractSearchResponse.class);
+
+ /**
+ * @return the {@link MavenSearchRepositoryType} type
+ */
+ public abstract MavenSearchRepositoryType getRepositoryType();
+
+ /**
+ * Creates a list of download URLs
+ *
+ * @return List of download links
+ * @throws MalformedURLException if an URL was not valid
+ */
+ public abstract List retrieveDownloadURLs() throws MalformedURLException;
+
+ /**
+ * Removes duplicates from list of download URLs
+ *
+ * @param downloadUrls list of download URLs
+ * @return List of download links
+ * @throws MalformedURLException if an URL was not valid
+ */
+ public List removeDuplicatedDownloadURLs(List downloadUrls) throws MalformedURLException {
+
+ return downloadUrls.stream().distinct().collect(Collectors.toList());
+ }
+
+ /**
+ * Retrieves the json response from a repository URL and a group ID
+ *
+ * @param repositoryUrl URL of the repository
+ * @param groupId to search for
+ * @return String of json response
+ * @throws RestSearchResponseException if the request did not return status 200
+ */
+ public String retrieveJsonResponse(String repositoryUrl, String groupId) throws RestSearchResponseException {
+
+ return retrieveJsonResponse(repositoryUrl, groupId, null);
+ }
+
+ /**
+ * Retrieves the json response from a repository URL, a group ID and a bearer authentication token
+ *
+ * @param repositoryUrl URL of the repository
+ * @param groupId to search for
+ * @param authToken bearer token to use for authentication
+ * @return String of json response
+ * @throws RestSearchResponseException if the request did not return status 200
+ */
+ public abstract String retrieveJsonResponse(String repositoryUrl, String groupId, String authToken)
+ throws RestSearchResponseException;
+
+ /**
+ * Creates a @WebTarget with provided authentication token
+ *
+ * @param targetLink link to get response from
+ * @param token bearer token to use for authentication
+ * @return Request to use as resource
+ */
+ private static Request bearerAuthenticationWithOAuth2AtClientLevel(String targetLink, String token) {
+
+ return new Request.Builder().url(targetLink).addHeader("Authorization", "Bearer " + token).build();
+
+ }
+
+ /**
+ * Retrieves a json response by given REST API target link using bearer authentication token
+ *
+ * @param targetLink link to get response from
+ * @param authToken bearer token to use for authentication
+ * @param searchRepositoryType the type of the search repository
+ * @return String of json response
+ * @throws RestSearchResponseException if the returned status code was not 200 OK
+ */
+ public static String retrieveJsonResponseWithAuthenticationToken(String targetLink, String authToken,
+ MavenSearchRepositoryType searchRepositoryType) throws RestSearchResponseException {
+
+ LOG.info("Starting {} search REST API request with URL: {}.", searchRepositoryType, targetLink);
+
+ OkHttpClient httpClient = new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS).callTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS)
+ .retryOnConnectionFailure(true).build();
+ String jsonResponse = "";
+
+ try {
+ Response response = null;
+
+ if (authToken != null) {
+ response = httpClient.newCall(bearerAuthenticationWithOAuth2AtClientLevel(targetLink, authToken)).execute();
+ } else {
+ response = httpClient.newCall(new Request.Builder().url(targetLink).get().build()).execute();
+ }
+
+ if (response != null) {
+ int status = response.code();
+ if (status == 200 || status == 201 || status == 204) {
+ jsonResponse = response.body().string();
+ } else {
+ throw new RestSearchResponseException("The search REST API returned the unexpected status code: ",
+ String.valueOf(response.code()));
+ }
+ }
+
+ } catch (IOException e) {
+ throw new RestSearchResponseException("Unable to send or receive the message from the service", e);
+ } catch (IllegalArgumentException e) {
+ throw new RestSearchResponseException("The target URL was faulty.", e);
+ }
+
+ return jsonResponse;
+
+ }
+
+ /**
+ * Creates a download link (concatenates maven repository link with groupId, artifact and version)
+ *
+ * @param mavenRepo link to the maven repository to use
+ * @param groupId for the download link
+ * @param artifactId for the download link
+ * @param version for the download link
+ * @param fileEnding file ending for the download link
+ * @return concatenated download link
+ * @throws MalformedURLException if the URL was not valid
+ */
+ protected static URL createDownloadLink(String mavenRepo, String groupId, String artifactId, String version,
+ String fileEnding) throws MalformedURLException {
+
+ String parsedGroupId = groupId.replace(".", "/");
+ String downloadFile = artifactId + "-" + version + fileEnding;
+ String downloadLink = mavenRepo + "/" + parsedGroupId + "/" + artifactId + "/" + version + "/" + downloadFile;
+ URL url = new URL(downloadLink);
+ return url;
+ }
+
+}
\ No newline at end of file
diff --git a/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/to/SearchResponseFactory.java b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/to/SearchResponseFactory.java
new file mode 100644
index 0000000000..4fd420d474
--- /dev/null
+++ b/cobigen/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/util/to/SearchResponseFactory.java
@@ -0,0 +1,72 @@
+package com.devonfw.cobigen.api.util.to;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.devonfw.cobigen.api.exception.RestSearchResponseException;
+import com.devonfw.cobigen.api.util.to.jfrog.JfrogSearchResponse;
+import com.devonfw.cobigen.api.util.to.maven.MavenSearchResponse;
+import com.devonfw.cobigen.api.util.to.nexus2.Nexus2SearchResponse;
+import com.devonfw.cobigen.api.util.to.nexus3.Nexus3SearchResponse;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.Lists;
+
+/**
+ * Factory to create new instances of {@link AbstractSearchResponse} which handles the responses from various search
+ * REST APIs.
+ */
+public class SearchResponseFactory {
+
+ /** Logger instance. */
+ static final Logger LOG = LoggerFactory.getLogger(SearchResponseFactory.class);
+
+ /**
+ * List of available {@link AbstractSearchResponse} implementations (add new search REST API responses here)
+ */
+ private static final List