Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d80f24e
Config files for s3 storage added
this-Aditya Feb 4, 2025
ab3fc8b
Gateway config merged with storage config and added conditional config
this-Aditya Feb 4, 2025
eee2d9c
Custom exception classes added
this-Aditya Feb 4, 2025
9b28499
New dependencies for min-io and multipart form handling
this-Aditya Feb 5, 2025
6c8f2a1
Resource enhancers for file storage created
this-Aditya Feb 5, 2025
9be4953
Path utils added
this-Aditya Feb 5, 2025
e5e0211
Minio loader created
this-Aditya Feb 5, 2025
7257a34
FileUploadResource added
this-Aditya Feb 5, 2025
dd95ffe
Filter created for blocking requests when file uploading is disabled
this-Aditya Feb 5, 2025
f3f0f3e
Added storage path
this-Aditya Feb 5, 2025
1bb7b1c
Created test case for storage path test
this-Aditya Feb 5, 2025
b4aa35a
Storage service and its test class added
this-Aditya Feb 5, 2025
23c6245
Updates to configuration files
this-Aditya Feb 5, 2025
7264ff9
Misc changes
this-Aditya Feb 5, 2025
be49bde
Changes for passing checks
this-Aditya Feb 5, 2025
dbeebf0
Fix ktlint
this-Aditya Feb 5, 2025
e63e4a8
Fix deprecated GitHub Action in workflow
this-Aditya Feb 5, 2025
f58350c
Using config values from environment variables if null
this-Aditya Feb 5, 2025
a5d26a4
Refactored builder pattern of StoragePath to data class
this-Aditya Feb 5, 2025
0284dda
RadarMinioClientLoader transformed to disposable supplier
this-Aditya Feb 6, 2025
8165dee
Misc changes
this-Aditya Feb 6, 2025
0e3e757
Using form params instead of path params
this-Aditya Feb 6, 2025
e72feca
Upated path annotation
this-Aditya Feb 6, 2025
1f60698
Added authentication and permissions on resource
this-Aditya Feb 6, 2025
f92588a
Handling aws regions for minio-client
this-Aditya Feb 10, 2025
11dba32
Corrected environment variables
this-Aditya Feb 10, 2025
3274ed0
Codestyle fix
this-Aditya Feb 10, 2025
a26838a
Addressed PR comments
this-Aditya Mar 6, 2025
38dbb03
Merge pull request #117 from RADAR-base/file-storage
yatharthranjan Mar 7, 2025
eb7497d
Fixing the logic for accessing environment variables in the config
this-Aditya Nov 8, 2025
e0eccdc
Using immutable properties
this-Aditya Nov 8, 2025
49b65f3
Ktlint format
this-Aditya Nov 8, 2025
36669d2
Move kafka healthcheck to dedicated coroutine dispatcher
mpgxvii Jan 13, 2026
a81ea32
Increase threadpool size according to grizzlyserver updates and impro…
mpgxvii Jan 16, 2026
6d4f253
Merge branch 'dev' of https://github.com/RADAR-base/RADAR-Gateway int…
mpgxvii Jan 16, 2026
e643e89
Fix merge conflicts
mpgxvii Jan 16, 2026
1dc79e7
Bump deps with vulnerabilities
mpgxvii Jan 16, 2026
7a286cf
Remove force upgrade on lz4
mpgxvii Jan 16, 2026
2aa0410
Revert minio update due to kotlin incompatibility
mpgxvii Jan 16, 2026
f2ebd29
Merge branch 'dev' into release-0.9.2
mpgxvii Jan 16, 2026
28a1e69
Merge pull request #129 from RADAR-base/release-0.9.2
mpgxvii Jan 16, 2026
ceac2eb
Merge branch 'dev' of https://github.com/RADAR-base/RADAR-Gateway int…
mpgxvii Jan 16, 2026
e01f33b
Increase coroutine IO dispatcher parallelism
mpgxvii Jan 20, 2026
6f729d5
Use radar-jersey main release version
mpgxvii Jan 20, 2026
e949f01
Add separate LivenessResource
mpgxvii Jan 21, 2026
7f9f1af
Use default IO dispatcher config
mpgxvii Jan 21, 2026
372972f
Merge pull request #128 from RADAR-base/fix/healthcheck
mpgxvii Jan 22, 2026
7cef351
Bump version
mpgxvii Jan 22, 2026
ee65ad0
Adding hk2 optional annotation for Storage Service
this-Aditya Jan 22, 2026
11641aa
Minor fixes
this-Aditya Jan 22, 2026
fafd1e2
Merge pull request #131 from RADAR-base/inject-optional
mpgxvii Jan 23, 2026
4d5153d
Misc changes
this-Aditya Jan 23, 2026
1b861b0
Ktlint format
this-Aditya Jan 23, 2026
e5b8b4a
Merge pull request #127 from RADAR-base/new-env-logic
this-Aditya Jan 23, 2026
73fe913
Merge branch 'dev' into release-0.9.3
this-Aditya Jan 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ plugins {

description = "RADAR Gateway to handle secured data flow to backend."

allprojects {
repositories {
mavenCentral()
maven(url = "https://central.sonatype.com/repository/maven-snapshots/") {
mavenContent {
snapshotsOnly()
}
}
}
}

radarRootProject {
projectVersion.set(Versions.project)
}
Expand Down
8 changes: 6 additions & 2 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
@Suppress("ConstPropertyName")
object Versions {
const val project = "0.9.2"
const val project = "0.9.3"

const val java = 17
const val kotlin = "1.9.22"
const val dockerCompose = "0.17.6"

const val ktor = "2.3.10"
const val radarJersey = "0.12.4"
const val radarJersey = "0.12.6"
const val radarCommons = "1.2.4"
const val radarSchemas = "0.8.14"
const val jackson = "2.17.2"
const val assertJ = "3.27.3"
const val mockk = "1.13.16"
const val log4j2 = "2.23.1"
const val lzfse = "0.1.1"
const val radarAuth = "2.1.12"
const val avro = "1.12.0"
const val confluent = "7.6.0"
const val kafka = "$confluent-ce"
const val minio = "8.5.10"
const val multipart = "3.1.10"

const val mockitoKotlin = "5.3.1"
const val grizzly = "4.0.2"
Expand Down
26 changes: 26 additions & 0 deletions gateway.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,29 @@ auth:
#rsa: []
# jwks URLs to fetch public keys from
#publicKeyUrls: []
# Storage settings for the application
storageCondition:
# Enables or disables file upload functionality in the application
fileUploadEnabled: true
# Specifies the type of storage used for storing uploaded files.
radarStorageType: s3
# Amazon S3 storage configurations
s3:
# The endpoint URL for the S3-compatible storage service.
# This can be a local MinIO server (e.g., http://localhost:9000) or AWS S3.
url: http://localhost:9000
# The access key used for authentication with the S3 storage.
accessKey: access-key
# The secret key used for authentication with the S3 storage.
secretKey: secret-key
# The name of the S3 bucket where files will be uploaded.
bucketName: radar
# specifies the region for the bucket
region: eu-west-2
# Path-related settings for organizing stored files.
path:
# A prefix to be added to the stored file paths (optional).
prefix:
# Determines whether files should be organized into daily subdirectories.
# If true, files will be stored in a structure like "bucketName/yyyy-MM-dd/".
collectPerDay: true
5 changes: 5 additions & 0 deletions radar-gateway/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,15 @@ dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))

implementation("org.glassfish.jersey.media:jersey-media-multipart:${Versions.multipart}")

implementation("org.radarbase:radar-commons:${Versions.radarCommons}")
implementation("org.radarbase:radar-commons-kotlin:${Versions.radarCommons}")
implementation("org.radarbase:radar-jersey:${Versions.radarJersey}")
implementation("org.radarbase:managementportal-client:${Versions.radarAuth}")
implementation("org.radarbase:lzfse-decode:${Versions.lzfse}")
implementation("org.radarbase:radar-auth:${Versions.radarAuth}")
implementation("io.minio:minio:${Versions.minio}")

implementation("org.apache.kafka:kafka-clients:${Versions.kafka}")
implementation("io.confluent:kafka-avro-serializer:${Versions.confluent}")
Expand All @@ -93,6 +96,8 @@ dependencies {
integrationTestImplementation("io.ktor:ktor-serialization-kotlinx-json")

testImplementation("org.radarbase:radar-schemas-commons:${Versions.radarSchemas}")
testImplementation("org.assertj:assertj-core:${Versions.assertJ}")
testImplementation("io.mockk:mockk:${Versions.mockk}")
integrationTestImplementation("org.radarbase:radar-schemas-commons:${Versions.radarSchemas}")
integrationTestImplementation("org.radarbase:radar-commons-testing:${Versions.radarCommons}")
}
Expand Down
26 changes: 26 additions & 0 deletions radar-gateway/src/integrationTest/docker/etc/gateway.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,29 @@ kafka:

auth:
managementPortalUrl: http://managementportal-app:8080/managementportal
# Storage settings for the application
storageCondition:
# Enables or disables file upload functionality in the application
fileUploadEnabled: false
# Specifies the type of storage used for storing uploaded files.
radarStorageType: s3
# Amazon S3 storage configurations
s3:
# The endpoint URL for the S3-compatible storage service.
# This can be a local MinIO server (e.g., http://localhost:9000) or AWS S3.
url: http://localhost:9000
# The access key used for authentication with the S3 storage.
# accessKey: access-key
# The secret key used for authentication with the S3 storage.
# secretKey: secret-key
# The name of the S3 bucket where files will be uploaded.
# bucketName: radar
# Path-related settings for organizing stored files.
# specifies the region for the bucket
# region: eu-west-2
path:
# A prefix to be added to the stored file paths (optional).
# prefix:
# Determines whether files should be organized into daily subdirectories.
# If true, files will be stored in a structure like "bucketName/yyyy-MM-dd/".
collectPerDay: true
10 changes: 9 additions & 1 deletion radar-gateway/src/main/kotlin/org/radarbase/gateway/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fun main(args: Array<String>) {
),
args,
).withDefaults()
.withEnv()
} catch (ex: IllegalArgumentException) {
logger.error("No configuration file was found.")
logger.error("Usage: radar-gateway <config-file>")
Expand All @@ -33,6 +34,13 @@ fun main(args: Array<String>) {
}

val resources = ConfigLoader.loadResources(config.resourceConfig, config)
val server = GrizzlyServer(config.server.baseUri, resources, config.server.isJmxEnabled)
val server = GrizzlyServer(
config.server.baseUri,
resources,
config.server.isJmxEnabled,
config.server.workerCorePoolSize,
config.server.workerMaxPoolSize,
)

server.listen()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.radarbase.gateway.config

import org.radarbase.gateway.inject.ManagementPortalEnhancerFactory
import org.radarbase.jersey.config.ConfigLoader.copyOnChange
import org.radarbase.jersey.enhancer.EnhancerFactory

data class GatewayConfig(
Expand All @@ -12,6 +13,10 @@ data class GatewayConfig(
val kafka: KafkaConfig = KafkaConfig(),
/** Server configurations. */
val server: GatewayServerConfig = GatewayServerConfig(),
/** AWS s3 storage configuration */
val s3: S3StorageConfig = S3StorageConfig(),
/** Whether to enable or disable the configurations based on the storage conditions */
val storageCondition: StorageConditionConfig = StorageConditionConfig(),
) {
/** Fill in some default values for the configuration. */
fun withDefaults(): GatewayConfig = copy(kafka = kafka.withDefaults())
Expand All @@ -24,4 +29,14 @@ data class GatewayConfig(
kafka.validate()
auth.validate()
}

fun withEnv(): GatewayConfig = this.copyOnChange(
s3,
{
it.withEnv()
},
{
copy(s3 = it)
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ data class GatewayServerConfig(
* Whether JMX should be enabled. Disable if not needed, for higher performance.
*/
val isJmxEnabled: Boolean = true,

/** Number of core worker threads in main thread pool */
val workerCorePoolSize: Int? = 32,

/** Max number of worker threads */
val workerMaxPoolSize: Int? = 128,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.radarbase.gateway.config

import org.radarbase.gateway.utils.Env.AWS_ACCESS_KEY_ID
import org.radarbase.gateway.utils.Env.AWS_DEFAULT_REGION
import org.radarbase.gateway.utils.Env.AWS_ENDPOINT_URL_S3
import org.radarbase.gateway.utils.Env.AWS_S3_BUCKET_NAME
import org.radarbase.gateway.utils.Env.AWS_SECRET_ACCESS_KEY
import org.radarbase.jersey.config.ConfigLoader.copyEnv
import org.radarbase.jersey.config.ConfigLoader.copyOnChange

data class S3StorageConfig(
val url: String? = null,
val accessKey: String? = null,
val secretKey: String? = null,
val bucketName: String? = null,
val region: String? = null,
val path: S3StoragePathConfig = S3StoragePathConfig(),
) {
fun withEnv(): S3StorageConfig = this.copyEnv(AWS_ENDPOINT_URL_S3) {
copy(url = it)
}.copyEnv(AWS_ACCESS_KEY_ID) {
copy(accessKey = it)
}.copyEnv(AWS_SECRET_ACCESS_KEY) {
copy(secretKey = it)
}.copyEnv(AWS_S3_BUCKET_NAME) {
copy(bucketName = it)
}.copyEnv(AWS_DEFAULT_REGION) {
copy(region = it)
}.copyOnChange(path, S3StoragePathConfig::withEnv) {
copy(path = it)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.radarbase.gateway.config

import org.radarbase.gateway.utils.Env.AWS_S3_PATH_PREFIX
import org.radarbase.jersey.config.ConfigLoader.copyEnv

data class S3StoragePathConfig(
val prefix: String? = null,
val collectPerDay: Boolean = true,
) {
fun withEnv(): S3StoragePathConfig = this
.copyEnv(AWS_S3_PATH_PREFIX) {
copy(prefix = it)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.radarbase.gateway.config

data class StorageConditionConfig(
val fileUploadEnabled: Boolean = true,
val radarStorageType: String = "s3",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.radarbase.gateway.exception

import jakarta.ws.rs.core.Response
import org.radarbase.jersey.exception.HttpApplicationException

class FileStorageException(message: String) :
HttpApplicationException(
Response.Status.INTERNAL_SERVER_ERROR,
Response.Status.INTERNAL_SERVER_ERROR.name.lowercase(),
message,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.radarbase.gateway.exception

import jakarta.ws.rs.core.Response
import org.radarbase.jersey.exception.HttpApplicationException

class InvalidFileDetailsException(message: String) :
HttpApplicationException(
Response.Status.EXPECTATION_FAILED,
Response.Status.EXPECTATION_FAILED.name.lowercase(),
message,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.radarbase.gateway.exception

import jakarta.ws.rs.core.Response
import org.radarbase.jersey.exception.HttpApplicationException

class InvalidPathDetailsException(message: String) :
HttpApplicationException(
Response.Status.EXPECTATION_FAILED,
Response.Status.EXPECTATION_FAILED.name.lowercase(),
message,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.radarbase.gateway.filter

import jakarta.annotation.Priority
import jakarta.inject.Singleton
import jakarta.ws.rs.Priorities
import jakarta.ws.rs.container.ContainerRequestContext
import jakarta.ws.rs.container.ContainerRequestFilter
import jakarta.ws.rs.core.Context
import jakarta.ws.rs.core.Response
import jakarta.ws.rs.ext.Provider
import org.radarbase.gateway.config.GatewayConfig
import org.radarbase.gateway.inject.ProcessFileUpload

@Provider
@Singleton
@Priority(Priorities.USER)
@ProcessFileUpload
class FileUploadFilter(
@Context private val config: GatewayConfig,
) : ContainerRequestFilter {

override fun filter(requestContext: ContainerRequestContext?) {
if (!config.storageCondition.fileUploadEnabled) {
requestContext?.abortWith(
Response.status(Response.Status.FORBIDDEN)
.entity("File uploading is not configured")
.build(),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.radarbase.gateway.inject

import jakarta.inject.Singleton
import org.glassfish.jersey.internal.inject.AbstractBinder
import org.glassfish.jersey.media.multipart.MultiPartFeature
import org.radarbase.gateway.config.GatewayConfig
import org.radarbase.gateway.config.S3StorageConfig
import org.radarbase.gateway.service.storage.RadarMinioClient
import org.radarbase.gateway.service.storage.RadarMinioClientFactory
import org.radarbase.gateway.service.storage.S3StorageService
import org.radarbase.gateway.service.storage.StorageService
import org.radarbase.jersey.enhancer.JerseyResourceEnhancer

class FileStorageEnhancer(
private val config: GatewayConfig,
) : JerseyResourceEnhancer {

override val classes: Array<Class<*>> = buildList(1) {
add(MultiPartFeature::class.java)
}.toTypedArray()

override fun AbstractBinder.enhance() {
bind(config.s3)
.to(S3StorageConfig::class.java)
.`in`(Singleton::class.java)

if (config.storageCondition.radarStorageType == "s3") {
bindFactory(RadarMinioClientFactory::class.java)
.to(RadarMinioClient::class.java)
.`in`(Singleton::class.java)

bind(S3StorageService::class.java)
.to(StorageService::class.java)
.`in`(Singleton::class.java)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ class ManagementPortalEnhancerFactory(private val config: GatewayConfig) : Enhan
jwtIssuer = config.auth.issuer,
jwksUrls = config.auth.publicKeyUrls ?: emptyList(),
)
return listOf(
GatewayResourceEnhancer(config),
Enhancers.radar(authConfig),
Enhancers.managementPortal(authConfig),
Enhancers.health,
Enhancers.exception,
)
return buildList {
add(GatewayResourceEnhancer(config))
add(Enhancers.radar(authConfig))
add(Enhancers.managementPortal(authConfig))
add(Enhancers.health)
add(Enhancers.exception)
if (config.storageCondition.fileUploadEnabled) {
add(FileStorageEnhancer(config))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.radarbase.gateway.inject

import jakarta.ws.rs.NameBinding

@NameBinding
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
)
@Retention(AnnotationRetention.RUNTIME)
annotation class ProcessFileUpload
Loading
Loading