Skip to content

Commit c4033c7

Browse files
committed
KTL-4173 Create RequestIndexingService (#249)
1 parent 01eaf29 commit c4033c7

10 files changed

Lines changed: 457 additions & 18 deletions

File tree

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ dependencies {
3131
implementation(projects.integrations.maven)
3232
implementation(projects.integrations.github)
3333

34+
implementation(libs.bundles.maven.indexer)
35+
3436
testImplementation(libs.okhttp)
3537
testImplementation(libs.kohsuke.githubApi)
3638
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package io.klibs.app.indexing
2+
3+
import io.klibs.app.util.toIndexRequest
4+
import io.klibs.core.pckg.repository.IndexingRequestRepository
5+
import io.klibs.core.pckg.repository.PackageRepository
6+
import io.klibs.integration.maven.MavenArtifact
7+
import io.klibs.integration.maven.ScraperType
8+
import io.klibs.integration.maven.search.ArtifactData
9+
import io.klibs.integration.maven.search.impl.CentralSonatypeSearchClient
10+
import io.klibs.integration.maven.search.paginateSearch
11+
import org.apache.maven.search.api.request.BooleanQuery
12+
import org.apache.maven.search.api.request.Query
13+
import org.slf4j.LoggerFactory
14+
import org.springframework.http.HttpStatus
15+
import org.springframework.stereotype.Service
16+
import org.springframework.transaction.annotation.Transactional
17+
import org.springframework.web.server.ResponseStatusException
18+
19+
@Service
20+
class RequestIndexingService(
21+
private val centralSonatypeSearchClient: CentralSonatypeSearchClient,
22+
private val indexingRequestRepository: IndexingRequestRepository,
23+
private val packageRepository: PackageRepository,
24+
) {
25+
/**
26+
* Discovers and saves packages for indexing after a manual request
27+
*
28+
* @param groupId Maven group ID (required)
29+
* @param artifactId Maven artifact ID (optional - if null, all artifacts in the group are indexed)
30+
* @param version Maven version (optional - if null, all versions of the artifact(s) are indexed)
31+
*/
32+
@Transactional
33+
fun requestIndexing(groupId: String, artifactId: String?, version: String?) {
34+
val artifacts = discoverArtifacts(groupId, artifactId, version)
35+
saveIndexRequests(artifacts)
36+
}
37+
38+
private fun discoverArtifacts(
39+
groupId: String,
40+
artifactId: String?,
41+
version: String?,
42+
): List<MavenArtifact> {
43+
if (artifactId != null && version != null) {
44+
return listOf(resolveSpecificVersion(groupId, artifactId, version))
45+
}
46+
47+
if (version != null) {
48+
logger.warn("Version is specified but artifactId is not. Ignoring version.")
49+
}
50+
51+
val query = buildKmpQuery(groupId, artifactId)
52+
val searchResult = paginateSearch(query)
53+
54+
if (searchResult.isEmpty()) {
55+
throw badRequest(
56+
"No Kotlin Multiplatform artifacts found for $groupId${
57+
artifactId?.let { ":$it" }.orEmpty()
58+
}"
59+
)
60+
}
61+
62+
val artifactsToSave = searchResult
63+
.map { it.toMavenArtifact() }
64+
.filterNot { isAlreadyIndexedOrQueued(it) }
65+
66+
if (artifactsToSave.isEmpty()) throw badRequest("All artifacts from this request are already indexed or queued")
67+
68+
return artifactsToSave
69+
}
70+
71+
private fun resolveSpecificVersion(groupId: String, artifactId: String, version: String): MavenArtifact {
72+
val artifact = MavenArtifact(groupId, artifactId, version, ScraperType.MANUAL_REQUEST)
73+
if (isAlreadyIndexedOrQueued(artifact)) throw badRequest("Artifact $groupId:$artifactId:$version is already indexed or queued")
74+
75+
centralSonatypeSearchClient.getKotlinToolingMetadata(artifact)
76+
?: throw badRequest(
77+
"Artifact $groupId:$artifactId:$version is not a valid Kotlin Multiplatform library " +
78+
"(kotlin-tooling-metadata.json not found)"
79+
)
80+
81+
return artifact
82+
}
83+
84+
private fun isAlreadyIndexedOrQueued(artifact: MavenArtifact): Boolean =
85+
with(artifact) {
86+
when {
87+
packageRepository.findByGroupIdAndArtifactIdAndVersion(groupId, artifactId, version) != null -> {
88+
logger.debug("Already indexed: $groupId:$artifactId:$version, skipping")
89+
true
90+
}
91+
92+
indexingRequestRepository.findByGroupIdAndArtifactIdAndVersion(groupId, artifactId, version) != null -> {
93+
logger.debug("Already queued: $groupId:$artifactId:$version, skipping")
94+
true
95+
}
96+
97+
else -> false
98+
}
99+
}
100+
101+
private fun buildKmpQuery(groupId: String, artifactId: String?): Query {
102+
var query = BooleanQuery.and(
103+
Query.query("g:$groupId"),
104+
Query.query("l:kotlin-tooling-metadata")
105+
)
106+
if (artifactId != null) {
107+
query = BooleanQuery.and(query, Query.query("a:$artifactId"))
108+
}
109+
return query
110+
}
111+
112+
private fun paginateSearch(query: Query): List<ArtifactData> =
113+
try {
114+
centralSonatypeSearchClient.paginateSearch(query).toList()
115+
} catch (e: Exception) {
116+
logger.error("Central Sonatype search failed: ${e.message}", e)
117+
throw ResponseStatusException(
118+
HttpStatus.SERVICE_UNAVAILABLE,
119+
"Central Sonatype search failed"
120+
)
121+
}
122+
123+
private fun saveIndexRequests(mavenArtifacts: List<MavenArtifact>) {
124+
val requests = mavenArtifacts.map { it.toIndexRequest() }
125+
126+
try {
127+
indexingRequestRepository.saveAll(requests)
128+
logger.info("Saved ${requests.size} index requests")
129+
} catch (e: Exception) {
130+
logger.error("Failed to save index requests: ${e.message}")
131+
132+
throw ResponseStatusException(
133+
HttpStatus.INTERNAL_SERVER_ERROR,
134+
"Failed to save index requests"
135+
)
136+
}
137+
}
138+
139+
private fun ArtifactData.toMavenArtifact() = MavenArtifact(
140+
groupId = groupId,
141+
artifactId = artifactId,
142+
version = version,
143+
scraperType = ScraperType.MANUAL_REQUEST,
144+
releasedAt = releasedAt,
145+
)
146+
147+
private fun badRequest(message: String): ResponseStatusException =
148+
ResponseStatusException(HttpStatus.BAD_REQUEST, message)
149+
150+
companion object {
151+
private val logger = LoggerFactory.getLogger(RequestIndexingService::class.java)
152+
}
153+
}

0 commit comments

Comments
 (0)