
toolkit.plugins.packagemanagers.bazel-package-manager.38.0.0.source-code.MultiBazelModuleRegistryService.kt Maven / Gradle / Ivy
/*
* Copyright (C) 2024 The ORT Project Authors (see )
*
* Licensed 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
*
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/
package org.ossreviewtoolkit.plugins.packagemanagers.bazel
import java.io.File
import org.apache.logging.log4j.kotlin.logger
import org.ossreviewtoolkit.clients.bazelmoduleregistry.BazelModuleRegistryService
import org.ossreviewtoolkit.clients.bazelmoduleregistry.DEFAULT_URL
import org.ossreviewtoolkit.clients.bazelmoduleregistry.LocalBazelModuleRegistryService
import org.ossreviewtoolkit.clients.bazelmoduleregistry.ModuleMetadata
import org.ossreviewtoolkit.clients.bazelmoduleregistry.ModuleSourceInfo
import org.ossreviewtoolkit.clients.bazelmoduleregistry.RemoteBazelModuleRegistryService
/**
* A special implementation of [BazelModuleRegistryService] that wraps an arbitrary number of other
* [BazelModuleRegistryService] instances. It can be used for projects that declare multiple registries.
*
* The functions of the interface are implemented by iterating over the wrapped services and returning the first
* successful result.
*/
internal class MultiBazelModuleRegistryService(
/** The wrapped [BazelModuleRegistryService] instances. */
private val registryServices: Collection
) : BazelModuleRegistryService {
companion object {
/**
* Create an instance of [MultiBazelModuleRegistryService] for the given [urls]. Based on the URLs, concrete
* [BazelModuleRegistryService] implementations are created. Local registry services use the given [projectDir]
* as workspace. These services are then queried in the order defined by the passed in collection. Note that as
* the last service a remote module registry for the Bazel Central Registry is added that serves as a fallback.
*/
fun create(urls: Collection, projectDir: File): MultiBazelModuleRegistryService {
// Add the default Bazel registry as a fallback.
val registryUrls = (urls + DEFAULT_URL).distinctBy { it.removeSuffix("/") }
logger.info { "Creating a multi Bazel module registry:" }
val registryServices = registryUrls.mapTo(mutableListOf()) { url ->
LocalBazelModuleRegistryService.create(url, projectDir)
?: RemoteBazelModuleRegistryService.create(url)
}
return MultiBazelModuleRegistryService(registryServices)
}
/**
* Return an exception with a message that combines the messages of all [Throwable]s in this list.
*/
private fun List.combinedException(caption: String): Throwable =
IllegalArgumentException(
"$caption:\n${joinToString("\n")}"
)
}
override val urls = registryServices.flatMap { it.urls }
override suspend fun getModuleMetadata(name: String): ModuleMetadata =
queryRegistryServices(
errorMessage = { "Failed to query metadata for package '$name'" },
query = { it.getModuleMetadata(name) }
)
override suspend fun getModuleSourceInfo(name: String, version: String): ModuleSourceInfo =
queryRegistryServices(
errorMessage = { "Failed to query source info for package '$name' and version '$version'" },
query = { it.getModuleSourceInfo(name, version) }
)
/**
* A generic function for sending a [query] to all managed [BazelModuleRegistryService] instances and returning the
* first successful result. In case no registry service can provide a result, throw an exception with the given
* [errorMessage] and a summary of all failures.
*/
private suspend fun queryRegistryServices(
errorMessage: () -> String,
query: suspend (BazelModuleRegistryService) -> T
): T {
val failures = mutableListOf()
tailrec suspend fun queryServices(itServices: Iterator): T? =
if (!itServices.hasNext()) {
null
} else {
val nextRegistry = itServices.next()
val triedResult = runCatching { query(nextRegistry) }
val result = triedResult.getOrNull()
// The Elvis operator does not work here because of the tailrec modifier.
if (result != null) {
result
} else {
triedResult.exceptionOrNull()?.let {
failures += "${nextRegistry.urls.joinToString()}: ${
it.message.orEmpty().ifEmpty { "" }
}"
}
queryServices(itServices)
}
}
val info = queryServices(registryServices.iterator())
return info ?: throw failures.combinedException(errorMessage())
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy