diff --git a/owl/.gitignore b/owl/.gitignore new file mode 100644 index 00000000..bf3e1b20 --- /dev/null +++ b/owl/.gitignore @@ -0,0 +1,43 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store +/.idea/ diff --git a/owl/broker/broker-mongodb/build.gradle.kts b/owl/broker/broker-mongodb/build.gradle.kts new file mode 100644 index 00000000..cd25913e --- /dev/null +++ b/owl/broker/broker-mongodb/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + application + kotlin("jvm") + id("com.google.devtools.ksp") version "1.9.22-1.0.17" +} + +apply { + plugin("com.google.devtools.ksp") +} + +group = "dev.usbharu" +version = "0.0.1" + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.0.0") + implementation(project(":broker")) + implementation(project(":common")) + implementation(platform("io.insert-koin:koin-bom:3.5.3")) + implementation(platform("io.insert-koin:koin-annotations-bom:1.3.1")) + implementation("io.insert-koin:koin-core") + compileOnly("io.insert-koin:koin-annotations") + ksp("io.insert-koin:koin-ksp-compiler:1.3.1") +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(17) +} + +application { + mainClass = "dev.usbharu.owl.broker.MainKt" +} \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModule.kt b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModule.kt new file mode 100644 index 00000000..33f40868 --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModule.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.mongodb + +import org.koin.core.annotation.ComponentScan +import org.koin.core.annotation.Module + +@Module +@ComponentScan("dev.usbharu.owl.broker.mongodb") +class MongoModule { +} + diff --git a/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModuleContext.kt b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModuleContext.kt new file mode 100644 index 00000000..e3ab269b --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModuleContext.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.mongodb + +import com.mongodb.ConnectionString +import com.mongodb.MongoClientSettings +import com.mongodb.kotlin.client.coroutine.MongoClient +import dev.usbharu.owl.broker.ModuleContext +import org.bson.UuidRepresentation +import org.koin.core.module.Module +import org.koin.dsl.module +import org.koin.ksp.generated.module + +class MongoModuleContext : ModuleContext { + override fun module(): Module { + val module = MongoModule().module + module.includes(module { + single { + val clientSettings = + MongoClientSettings.builder() + .applyConnectionString( + ConnectionString( + System.getProperty( + "owl.broker.mongo.url", + "mongodb://localhost:27017" + ) + ) + ) + .uuidRepresentation(UuidRepresentation.STANDARD).build() + + + MongoClient.create(clientSettings) + .getDatabase(System.getProperty("owl.broker.mongo.database", "mongo-test")) + } + }) + return module + } +} \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepository.kt b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepository.kt new file mode 100644 index 00000000..43207580 --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepository.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.mongodb + +import com.mongodb.client.model.Filters +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.kotlin.client.coroutine.MongoDatabase +import dev.usbharu.owl.broker.domain.model.consumer.Consumer +import dev.usbharu.owl.broker.domain.model.consumer.ConsumerRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.singleOrNull +import kotlinx.coroutines.withContext +import org.bson.BsonType +import org.bson.codecs.pojo.annotations.BsonId +import org.bson.codecs.pojo.annotations.BsonRepresentation +import org.koin.core.annotation.Singleton +import java.util.* + +@Singleton +class MongodbConsumerRepository(database: MongoDatabase) : ConsumerRepository { + + private val collection = database.getCollection("consumers") + override suspend fun save(consumer: Consumer): Consumer = withContext(Dispatchers.IO) { + collection.replaceOne(Filters.eq("_id", consumer.id.toString()), ConsumerMongodb.of(consumer), ReplaceOptions().upsert(true)) + return@withContext consumer + } + + override suspend fun findById(id: UUID): Consumer? = withContext(Dispatchers.IO) { + return@withContext collection.find(Filters.eq("_id", id.toString())).singleOrNull()?.toConsumer() + } +} + +data class ConsumerMongodb( + @BsonId + @BsonRepresentation(BsonType.STRING) + val id: String, + val name: String, + val hostname: String, + val tasks: List +){ + + fun toConsumer():Consumer{ + return Consumer( + UUID.fromString(id), name, hostname, tasks + ) + } + companion object{ + fun of(consumer: Consumer):ConsumerMongodb{ + return ConsumerMongodb( + consumer.id.toString(), + consumer.name, + consumer.hostname, + consumer.tasks + ) + } + } +} \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbProducerRepository.kt b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbProducerRepository.kt new file mode 100644 index 00000000..3593e5f8 --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbProducerRepository.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.mongodb + +import com.mongodb.client.model.Filters +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.kotlin.client.coroutine.MongoDatabase +import dev.usbharu.owl.broker.domain.model.producer.Producer +import dev.usbharu.owl.broker.domain.model.producer.ProducerRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.koin.core.annotation.Singleton +import java.time.Instant +import java.util.* + +@Singleton +class MongodbProducerRepository(database: MongoDatabase) : ProducerRepository { + + private val collection = database.getCollection("producers") + + override suspend fun save(producer: Producer): Producer = withContext(Dispatchers.IO) { + collection.replaceOne( + Filters.eq("_id", producer.id.toString()), + ProducerMongodb.of(producer), + ReplaceOptions().upsert(true) + ) + return@withContext producer + } +} + +data class ProducerMongodb( + val id: String, + val name: String, + val hostname: String, + val registeredTask: List, + val createdAt: Instant +) { + fun toProducer(): Producer { + return Producer( + id = UUID.fromString(id), + name = name, + hostname = hostname, + registeredTask = registeredTask, + createdAt = createdAt + ) + } + + companion object { + fun of(producer: Producer): ProducerMongodb { + return ProducerMongodb( + id = producer.id.toString(), + name = producer.name, + hostname = producer.hostname, + registeredTask = producer.registeredTask, + createdAt = producer.createdAt + ) + } + } +} \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbQueuedTaskRepository.kt b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbQueuedTaskRepository.kt new file mode 100644 index 00000000..343b5c21 --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbQueuedTaskRepository.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.mongodb + +import com.mongodb.client.model.Filters.* +import com.mongodb.client.model.FindOneAndUpdateOptions +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.client.model.ReturnDocument +import com.mongodb.client.model.Sorts +import com.mongodb.client.model.Updates.set +import com.mongodb.kotlin.client.coroutine.MongoDatabase +import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask +import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTaskRepository +import dev.usbharu.owl.broker.domain.model.task.Task +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.property.PropertySerializerFactory +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import org.bson.BsonType +import org.bson.codecs.pojo.annotations.BsonId +import org.bson.codecs.pojo.annotations.BsonRepresentation +import org.koin.core.annotation.Singleton +import java.time.Instant +import java.util.* + +@Singleton +class MongodbQueuedTaskRepository( + private val propertySerializerFactory: PropertySerializerFactory, + database: MongoDatabase +) : QueuedTaskRepository { + + private val collection = database.getCollection("queued_task") + override suspend fun save(queuedTask: QueuedTask): QueuedTask { + withContext(Dispatchers.IO) { + collection.replaceOne( + eq("_id", queuedTask.task.id.toString()), QueuedTaskMongodb.of(propertySerializerFactory, queuedTask), + ReplaceOptions().upsert(true) + ) + } + return queuedTask + } + + override suspend fun findByTaskIdAndAssignedConsumerIsNullAndUpdate(id: UUID, update: QueuedTask): QueuedTask { + return withContext(Dispatchers.IO) { + + val findOneAndUpdate = collection.findOneAndUpdate( + and( + eq("_id", id.toString()), + eq(QueuedTaskMongodb::isActive.name, true) + ), + listOf( + set(QueuedTaskMongodb::assignedConsumer.name, update.assignedConsumer), + set(QueuedTaskMongodb::assignedAt.name, update.assignedAt), + set(QueuedTaskMongodb::queuedAt.name, update.queuedAt), + set(QueuedTaskMongodb::isActive.name, update.isActive) + ), + FindOneAndUpdateOptions().upsert(false).returnDocument(ReturnDocument.AFTER) + ) + if (findOneAndUpdate == null) { + TODO() + } + findOneAndUpdate.toQueuedTask(propertySerializerFactory) + } + } + + override fun findByTaskNameInAndIsActiveIsTrueAndOrderByPriority( + tasks: List, + limit: Int + ): Flow { + return collection.find( + and( + `in`("task.name", tasks), + eq(QueuedTaskMongodb::isActive.name, true) + ) + ).sort(Sorts.descending("priority")).map { it.toQueuedTask(propertySerializerFactory) }.flowOn(Dispatchers.IO) + } + + override fun findByQueuedAtBeforeAndIsActiveIsTrue(instant: Instant): Flow { + return collection.find( + and( + lte(QueuedTaskMongodb::queuedAt.name, instant), + eq(QueuedTaskMongodb::isActive.name, true) + ) + ) + .map { it.toQueuedTask(propertySerializerFactory) }.flowOn(Dispatchers.IO) + } +} + +data class QueuedTaskMongodb( + @BsonId + @BsonRepresentation(BsonType.STRING) + val id: String, + val task: TaskMongodb, + val attempt: Int, + val queuedAt: Instant, + val priority:Int, + val isActive: Boolean, + val timeoutAt: Instant?, + val assignedConsumer: String?, + val assignedAt: Instant? +) { + + fun toQueuedTask(propertySerializerFactory: PropertySerializerFactory): QueuedTask { + return QueuedTask( + attempt = attempt, + queuedAt = queuedAt, + task = task.toTask(propertySerializerFactory), + priority = priority, + isActive = isActive, + timeoutAt = timeoutAt, + assignedConsumer = assignedConsumer?.let { UUID.fromString(it) }, + assignedAt = assignedAt + ) + } + + data class TaskMongodb( + val name: String, + val id: String, + val publishProducerId: String, + val publishedAt: Instant, + val nextRetry: Instant, + val completedAt: Instant?, + val attempt: Int, + val properties: Map + ) { + + fun toTask(propertySerializerFactory: PropertySerializerFactory): Task { + return Task( + name = name, + id = UUID.fromString(id), + publishProducerId = UUID.fromString(publishProducerId), + publishedAt = publishedAt, + nextRetry = nextRetry, + completedAt = completedAt, + attempt = attempt, + properties = PropertySerializeUtils.deserialize(propertySerializerFactory, properties) + ) + } + + companion object { + fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb { + return TaskMongodb( + task.name, + task.id.toString(), + task.publishProducerId.toString(), + task.publishedAt, + task.nextRetry, + task.completedAt, + task.attempt, + PropertySerializeUtils.serialize(propertySerializerFactory, task.properties) + ) + } + } + } + + companion object { + fun of(propertySerializerFactory: PropertySerializerFactory, queuedTask: QueuedTask): QueuedTaskMongodb { + return QueuedTaskMongodb( + queuedTask.task.id.toString(), + TaskMongodb.of(propertySerializerFactory, queuedTask.task), + queuedTask.attempt, + queuedTask.queuedAt, + queuedTask.priority, + queuedTask.isActive, + queuedTask.timeoutAt, + queuedTask.assignedConsumer?.toString(), + queuedTask.assignedAt + ) + } + } +} \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskDefinitionRepository.kt b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskDefinitionRepository.kt new file mode 100644 index 00000000..ced2b19a --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskDefinitionRepository.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.mongodb + +import com.mongodb.client.model.Filters +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.kotlin.client.coroutine.MongoDatabase +import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinition +import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinitionRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.singleOrNull +import kotlinx.coroutines.withContext +import org.bson.BsonType +import org.bson.codecs.pojo.annotations.BsonId +import org.bson.codecs.pojo.annotations.BsonRepresentation +import org.koin.core.annotation.Singleton + +@Singleton +class MongodbTaskDefinitionRepository(database: MongoDatabase) : TaskDefinitionRepository { + + private val collection = database.getCollection("task_definition") + override suspend fun save(taskDefinition: TaskDefinition): TaskDefinition = withContext(Dispatchers.IO) { + collection.replaceOne( + Filters.eq("_id", taskDefinition.name), + TaskDefinitionMongodb.of(taskDefinition), + ReplaceOptions().upsert(true) + ) + return@withContext taskDefinition + } + + override suspend fun deleteByName(name: String): Unit = withContext(Dispatchers.IO) { + collection.deleteOne(Filters.eq("_id",name)) + } + + override suspend fun findByName(name: String): TaskDefinition? = withContext(Dispatchers.IO) { + return@withContext collection.find(Filters.eq("_id", name)).singleOrNull()?.toTaskDefinition() + } +} + +data class TaskDefinitionMongodb( + @BsonId + @BsonRepresentation(BsonType.STRING) + val name: String, + val priority: Int, + val maxRetry: Int, + val timeoutMilli: Long, + val propertyDefinitionHash: Long, + val retryPolicy: String +) { + fun toTaskDefinition(): TaskDefinition { + return TaskDefinition( + name = name, + priority = priority, + maxRetry = maxRetry, + timeoutMilli = timeoutMilli, + propertyDefinitionHash = propertyDefinitionHash, + retryPolicy = retryPolicy + ) + } + + companion object { + fun of(taskDefinition: TaskDefinition): TaskDefinitionMongodb { + return TaskDefinitionMongodb( + name = taskDefinition.name, + priority = taskDefinition.priority, + maxRetry = taskDefinition.maxRetry, + timeoutMilli = taskDefinition.timeoutMilli, + propertyDefinitionHash = taskDefinition.propertyDefinitionHash, + retryPolicy = taskDefinition.retryPolicy + ) + } + } +} \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskRepository.kt b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskRepository.kt new file mode 100644 index 00000000..2d7215ae --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskRepository.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.mongodb + +import com.mongodb.client.model.Filters +import com.mongodb.client.model.ReplaceOneModel +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.kotlin.client.coroutine.MongoDatabase +import dev.usbharu.owl.broker.domain.model.task.Task +import dev.usbharu.owl.broker.domain.model.task.TaskRepository +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.property.PropertySerializerFactory +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.singleOrNull +import kotlinx.coroutines.withContext +import org.bson.BsonType +import org.bson.codecs.pojo.annotations.BsonId +import org.bson.codecs.pojo.annotations.BsonRepresentation +import org.koin.core.annotation.Singleton +import java.time.Instant +import java.util.* + +@Singleton +class MongodbTaskRepository(database: MongoDatabase, private val propertySerializerFactory: PropertySerializerFactory) : + TaskRepository { + + private val collection = database.getCollection("tasks") + override suspend fun save(task: Task): Task = withContext(Dispatchers.IO) { + collection.replaceOne( + Filters.eq("_id", task.id.toString()), TaskMongodb.of(propertySerializerFactory, task), + ReplaceOptions().upsert(true) + ) + return@withContext task + } + + override suspend fun saveAll(tasks: List): Unit = withContext(Dispatchers.IO) { + collection.bulkWrite(tasks.map { + ReplaceOneModel( + Filters.eq(it.id.toString()), + TaskMongodb.of(propertySerializerFactory, it), + ReplaceOptions().upsert(true) + ) + }) + } + + override fun findByNextRetryBeforeAndCompletedAtIsNull(timestamp: Instant): Flow { + return collection.find( + Filters.and( + Filters.lte(TaskMongodb::nextRetry.name, timestamp), + Filters.eq(TaskMongodb::completedAt.name, null) + ) + ) + .map { it.toTask(propertySerializerFactory) }.flowOn(Dispatchers.IO) + } + + override suspend fun findById(uuid: UUID): Task? = withContext(Dispatchers.IO) { + collection.find(Filters.eq(uuid.toString())).singleOrNull()?.toTask(propertySerializerFactory) + } + + override suspend fun findByIdAndUpdate(id: UUID, task: Task) { + collection.replaceOne( + Filters.eq("_id", task.id.toString()), TaskMongodb.of(propertySerializerFactory, task), + ReplaceOptions().upsert(false) + ) + } + + override suspend fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId: UUID): Flow { + return collection + .find(Filters.eq(TaskMongodb::publishProducerId.name, publishProducerId.toString())) + .map { it.toTask(propertySerializerFactory) } + } +} + +data class TaskMongodb( + val name: String, + @BsonId + @BsonRepresentation(BsonType.STRING) + val id: String, + val publishProducerId: String, + val publishedAt: Instant, + val nextRetry: Instant, + val completedAt: Instant?, + val attempt: Int, + val properties: Map +) { + + fun toTask(propertySerializerFactory: PropertySerializerFactory): Task { + return Task( + name = name, + id = UUID.fromString(id), + publishProducerId = UUID.fromString(publishProducerId), + publishedAt = publishedAt, + nextRetry = nextRetry, + completedAt = completedAt, + attempt = attempt, + properties = PropertySerializeUtils.deserialize(propertySerializerFactory, properties) + ) + } + + companion object { + fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb { + return TaskMongodb( + task.name, + task.id.toString(), + task.publishProducerId.toString(), + task.publishedAt, + task.nextRetry, + task.completedAt, + task.attempt, + PropertySerializeUtils.serialize(propertySerializerFactory, task.properties) + ) + } + } +} \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskResultRepository.kt b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskResultRepository.kt new file mode 100644 index 00000000..ed000fe2 --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskResultRepository.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.mongodb + +import com.mongodb.client.model.Filters +import com.mongodb.client.model.ReplaceOptions +import com.mongodb.kotlin.client.coroutine.MongoDatabase +import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult +import dev.usbharu.owl.broker.domain.model.taskresult.TaskResultRepository +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.property.PropertySerializerFactory +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import org.bson.BsonType +import org.bson.codecs.pojo.annotations.BsonId +import org.bson.codecs.pojo.annotations.BsonRepresentation +import org.koin.core.annotation.Singleton +import java.util.* + +@Singleton +class MongodbTaskResultRepository( + database: MongoDatabase, + private val propertySerializerFactory: PropertySerializerFactory +) : TaskResultRepository { + + private val collection = database.getCollection("task_results") + override suspend fun save(taskResult: TaskResult): TaskResult = withContext(Dispatchers.IO) { + collection.replaceOne( + Filters.eq(taskResult.id.toString()), TaskResultMongodb.of(propertySerializerFactory, taskResult), + ReplaceOptions().upsert(true) + ) + return@withContext taskResult + } + + override fun findByTaskId(id: UUID): Flow { + return collection.find(Filters.eq(id.toString())).map { it.toTaskResult(propertySerializerFactory) }.flowOn(Dispatchers.IO) + } +} + +data class TaskResultMongodb( + @BsonId + @BsonRepresentation(BsonType.STRING) + val id: String, + val taskId: String, + val success: Boolean, + val attempt: Int, + val result: Map, + val message: String +) { + + fun toTaskResult(propertySerializerFactory: PropertySerializerFactory): TaskResult { + return TaskResult( + UUID.fromString(id), + UUID.fromString(taskId), + success, + attempt, + PropertySerializeUtils.deserialize(propertySerializerFactory, result), + message + ) + } + + companion object { + fun of(propertySerializerFactory: PropertySerializerFactory, taskResult: TaskResult): TaskResultMongodb { + return TaskResultMongodb( + taskResult.id.toString(), + taskResult.taskId.toString(), + taskResult.success, + taskResult.attempt, + PropertySerializeUtils.serialize(propertySerializerFactory, taskResult.result), + taskResult.message + ) + + } + + } +} \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/main/resources/META-INF/services/dev.usbharu.owl.broker.ModuleContext b/owl/broker/broker-mongodb/src/main/resources/META-INF/services/dev.usbharu.owl.broker.ModuleContext new file mode 100644 index 00000000..0a1dface --- /dev/null +++ b/owl/broker/broker-mongodb/src/main/resources/META-INF/services/dev.usbharu.owl.broker.ModuleContext @@ -0,0 +1 @@ +dev.usbharu.owl.broker.mongodb.MongoModuleContext \ No newline at end of file diff --git a/owl/broker/broker-mongodb/src/test/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepositoryTest.kt b/owl/broker/broker-mongodb/src/test/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepositoryTest.kt new file mode 100644 index 00000000..b71c5ea4 --- /dev/null +++ b/owl/broker/broker-mongodb/src/test/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepositoryTest.kt @@ -0,0 +1,48 @@ +package dev.usbharu.owl.broker.mongodb + +import com.mongodb.ConnectionString +import com.mongodb.MongoClientSettings +import com.mongodb.kotlin.client.coroutine.MongoClient +import dev.usbharu.owl.broker.domain.model.consumer.Consumer +import kotlinx.coroutines.runBlocking +import org.bson.UuidRepresentation +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.* + + +class MongodbConsumerRepositoryTest { + @Test + fun name() { + + val clientSettings = + MongoClientSettings.builder().applyConnectionString(ConnectionString("mongodb://localhost:27017")) + .uuidRepresentation(UuidRepresentation.STANDARD).build() + + + val database = MongoClient.create(clientSettings).getDatabase("mongo-test") + + val mongodbConsumerRepository = MongodbConsumerRepository(database) + + val consumer = Consumer( + UUID.randomUUID(), + name = "test", + hostname = "aaa", + tasks = listOf("a", "b", "c") + ) + runBlocking { + mongodbConsumerRepository.save(consumer) + + val findById = mongodbConsumerRepository.findById(UUID.randomUUID()) + assertEquals(null, findById) + + val findById1 = mongodbConsumerRepository.findById(consumer.id) + assertEquals(consumer, findById1) + + mongodbConsumerRepository.save(consumer.copy(name = "test2")) + + val findById2 = mongodbConsumerRepository.findById(consumer.id) + assertEquals(consumer.copy(name = "test2"), findById2) + } + } +} \ No newline at end of file diff --git a/owl/broker/build.gradle.kts b/owl/broker/build.gradle.kts new file mode 100644 index 00000000..1d3d402e --- /dev/null +++ b/owl/broker/build.gradle.kts @@ -0,0 +1,64 @@ +plugins { + kotlin("jvm") + id("com.google.protobuf") version "0.9.4" + id("com.google.devtools.ksp") version "1.9.22-1.0.17" +} + +apply { + plugin("com.google.devtools.ksp") +} + + +group = "dev.usbharu" +version = "0.0.1" + +repositories { + mavenCentral() +} + +dependencies { + implementation("io.grpc:grpc-kotlin-stub:1.4.1") + implementation("io.grpc:grpc-protobuf:1.61.1") + implementation("com.google.protobuf:protobuf-kotlin:3.25.3") + implementation("io.grpc:grpc-netty:1.61.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") + implementation(project(":common")) + implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.23.0") + implementation(platform("io.insert-koin:koin-bom:3.5.3")) + implementation(platform("io.insert-koin:koin-annotations-bom:1.3.1")) + implementation("io.insert-koin:koin-core") + compileOnly("io.insert-koin:koin-annotations") + ksp("io.insert-koin:koin-ksp-compiler:1.3.1") +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(17) +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.25.3" + } + plugins { + create("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:1.61.1" + } + create("grpckt") { + artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar" + } + } + generateProtoTasks { + all().forEach { + it.plugins { + create("grpc") + create("grpckt") + } + it.builtins { + create("kotlin") + } + } + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/Main.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/Main.kt new file mode 100644 index 00000000..2dd9e400 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/Main.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker + +import dev.usbharu.owl.broker.service.DefaultRetryPolicyFactory +import dev.usbharu.owl.broker.service.RetryPolicyFactory +import dev.usbharu.owl.common.retry.ExponentialRetryPolicy +import kotlinx.coroutines.runBlocking +import org.koin.core.context.startKoin +import org.koin.dsl.module +import org.koin.ksp.generated.defaultModule +import org.slf4j.LoggerFactory +import java.util.* + +val logger = LoggerFactory.getLogger("MAIN") + +fun main() { + val moduleContexts = ServiceLoader.load(ModuleContext::class.java) + + val moduleContext = moduleContexts.first() + + logger.info("Use module name: {}", moduleContext) + + + val koin = startKoin { + printLogger() + + val module = module { + single { + DefaultRetryPolicyFactory(mapOf("" to ExponentialRetryPolicy())) + } + } + modules(module, defaultModule, moduleContext.module()) + } + + val application = koin.koin.get() + + runBlocking { + application.start(50051).join() + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/ModuleContext.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/ModuleContext.kt new file mode 100644 index 00000000..1478f4f9 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/ModuleContext.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker + +import org.koin.core.module.Module + +interface ModuleContext { + fun module():Module +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/OwlBrokerApplication.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/OwlBrokerApplication.kt new file mode 100644 index 00000000..66696f23 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/OwlBrokerApplication.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker + +import dev.usbharu.owl.broker.interfaces.grpc.* +import dev.usbharu.owl.broker.service.TaskManagementService +import io.grpc.Server +import io.grpc.ServerBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import org.koin.core.annotation.Singleton + +@Singleton +class OwlBrokerApplication( + private val assignmentTaskService: AssignmentTaskService, + private val definitionTaskService: DefinitionTaskService, + private val producerService: ProducerService, + private val subscribeTaskService: SubscribeTaskService, + private val taskPublishService: TaskPublishService, + private val taskManagementService: TaskManagementService, + private val taskResultSubscribeService: TaskResultSubscribeService +) { + + private lateinit var server: Server + + fun start(port: Int,coroutineScope: CoroutineScope = GlobalScope):Job { + server = ServerBuilder.forPort(port) + .addService(assignmentTaskService) + .addService(definitionTaskService) + .addService(producerService) + .addService(subscribeTaskService) + .addService(taskPublishService) + .addService(taskResultSubscribeService) + .build() + + server.start() + Runtime.getRuntime().addShutdownHook( + Thread { + server.shutdown() + } + ) + + return coroutineScope.launch { + taskManagementService.startManagement(this) + } + } + + fun stop() { + server.shutdown() + } + +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/FailedSaveException.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/FailedSaveException.kt new file mode 100644 index 00000000..4cb68305 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/FailedSaveException.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.exception.repository + +class FailedSaveException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/RecordNotFoundException.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/RecordNotFoundException.kt new file mode 100644 index 00000000..62921c46 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/RecordNotFoundException.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.exception.repository + +open class RecordNotFoundException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/IncompatibleTaskException.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/IncompatibleTaskException.kt new file mode 100644 index 00000000..9f940bc2 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/IncompatibleTaskException.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.exception.service + +class IncompatibleTaskException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/QueueCannotDequeueException.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/QueueCannotDequeueException.kt new file mode 100644 index 00000000..49b47dbf --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/QueueCannotDequeueException.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.exception.service + +class QueueCannotDequeueException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/RetryPolicyNotFoundException.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/RetryPolicyNotFoundException.kt new file mode 100644 index 00000000..dd6954b0 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/RetryPolicyNotFoundException.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.exception.service + +class RetryPolicyNotFoundException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/TaskNotRegisterException.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/TaskNotRegisterException.kt new file mode 100644 index 00000000..ca894165 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/TaskNotRegisterException.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.exception.service + +class TaskNotRegisterException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/Consumer.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/Consumer.kt new file mode 100644 index 00000000..27fe78b2 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/Consumer.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.consumer + +import java.util.* + +data class Consumer( + val id: UUID, + val name: String, + val hostname: String, + val tasks: List +) diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/ConsumerRepository.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/ConsumerRepository.kt new file mode 100644 index 00000000..34ebb0af --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/ConsumerRepository.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.consumer + +import java.util.* + +interface ConsumerRepository { + suspend fun save(consumer: Consumer):Consumer + + suspend fun findById(id:UUID):Consumer? +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/Producer.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/Producer.kt new file mode 100644 index 00000000..0a5e4916 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/Producer.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.producer + +import java.time.Instant +import java.util.UUID + +data class Producer( + val id:UUID, + val name:String, + val hostname:String, + val registeredTask:List, + val createdAt: Instant +) diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/ProducerRepository.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/ProducerRepository.kt new file mode 100644 index 00000000..932cef10 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/ProducerRepository.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.producer + +interface ProducerRepository { + suspend fun save(producer: Producer):Producer +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTask.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTask.kt new file mode 100644 index 00000000..b9dab655 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTask.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.queuedtask + +import dev.usbharu.owl.broker.domain.model.task.Task +import java.time.Instant +import java.util.* + +/** + * @param attempt キューされた時点での試行回数より1多い + * @param isActive trueならアサイン可能 falseならアサイン済みかタイムアウト等で無効 + */ +data class QueuedTask( + val attempt: Int, + val queuedAt: Instant, + val task: Task, + val priority: Int, + val isActive: Boolean, + val timeoutAt: Instant?, + val assignedConsumer: UUID?, + val assignedAt: Instant? +) diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTaskRepository.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTaskRepository.kt new file mode 100644 index 00000000..9f3c12a7 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTaskRepository.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.queuedtask + +import kotlinx.coroutines.flow.Flow +import java.time.Instant +import java.util.* + +interface QueuedTaskRepository { + suspend fun save(queuedTask: QueuedTask):QueuedTask + + /** + * トランザクションの代わり + */ + suspend fun findByTaskIdAndAssignedConsumerIsNullAndUpdate(id:UUID,update:QueuedTask):QueuedTask + + fun findByTaskNameInAndIsActiveIsTrueAndOrderByPriority(tasks: List, limit: Int): Flow + + fun findByQueuedAtBeforeAndIsActiveIsTrue(instant: Instant): Flow +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/Task.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/Task.kt new file mode 100644 index 00000000..8bf3a162 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/Task.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.task + +import dev.usbharu.owl.common.property.PropertyValue +import java.time.Instant +import java.util.* + +/** + * @param attempt 失敗を含めて試行した回数 + */ +data class Task( + val name:String, + val id: UUID, + val publishProducerId:UUID, + val publishedAt: Instant, + val nextRetry:Instant, + val completedAt: Instant? = null, + val attempt: Int, + val properties: Map> +) diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/TaskRepository.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/TaskRepository.kt new file mode 100644 index 00000000..009dea2d --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/TaskRepository.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.task + +import kotlinx.coroutines.flow.Flow +import java.time.Instant +import java.util.* + +interface TaskRepository { + suspend fun save(task: Task):Task + + suspend fun saveAll(tasks:List) + + fun findByNextRetryBeforeAndCompletedAtIsNull(timestamp:Instant): Flow + + suspend fun findById(uuid: UUID): Task? + + suspend fun findByIdAndUpdate(id:UUID,task: Task) + + suspend fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId:UUID):Flow +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinition.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinition.kt new file mode 100644 index 00000000..8fcb8f1e --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinition.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.taskdefinition + +data class TaskDefinition( + val name: String, + val priority: Int, + val maxRetry: Int, + val timeoutMilli: Long, + val propertyDefinitionHash: Long, + val retryPolicy:String +) diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinitionRepository.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinitionRepository.kt new file mode 100644 index 00000000..2a1c3c6a --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinitionRepository.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.taskdefinition + +interface TaskDefinitionRepository { + suspend fun save(taskDefinition: TaskDefinition): TaskDefinition + suspend fun deleteByName(name:String) + + suspend fun findByName(name:String):TaskDefinition? +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResult.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResult.kt new file mode 100644 index 00000000..b024d04c --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResult.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.taskresult + +import dev.usbharu.owl.common.property.PropertyValue +import java.util.* + +data class TaskResult( + val id: UUID, + val taskId:UUID, + val success: Boolean, + val attempt: Int, + val result: Map>, + val message: String +) diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResultRepository.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResultRepository.kt new file mode 100644 index 00000000..4118cfed --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResultRepository.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.domain.model.taskresult + +import kotlinx.coroutines.flow.Flow +import java.util.* + +interface TaskResultRepository { + suspend fun save(taskResult: TaskResult):TaskResult + fun findByTaskId(id:UUID): Flow +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/external/GrpcExtension.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/external/GrpcExtension.kt new file mode 100644 index 00000000..4271b55c --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/external/GrpcExtension.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.external + +import com.google.protobuf.Timestamp +import dev.usbharu.owl.Uuid +import java.time.Instant +import java.util.* + +fun Uuid.UUID.toUUID(): UUID = UUID(mostSignificantUuidBits, leastSignificantUuidBits) + +fun UUID.toUUID(): Uuid.UUID = Uuid + .UUID + .newBuilder() + .setMostSignificantUuidBits(mostSignificantBits) + .setLeastSignificantUuidBits(leastSignificantBits) + .build() + +fun Timestamp.toInstant(): Instant = Instant.ofEpochSecond(seconds, nanos.toLong()) + +fun Instant.toTimestamp():Timestamp = Timestamp.newBuilder().setSeconds(this.epochSecond).setNanos(this.nano).build() \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/AssignmentTaskService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/AssignmentTaskService.kt new file mode 100644 index 00000000..a1840588 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/AssignmentTaskService.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.interfaces.grpc + +import dev.usbharu.owl.AssignmentTaskServiceGrpcKt.AssignmentTaskServiceCoroutineImplBase +import dev.usbharu.owl.Task +import dev.usbharu.owl.Task.TaskRequest +import dev.usbharu.owl.broker.external.toTimestamp +import dev.usbharu.owl.broker.external.toUUID +import dev.usbharu.owl.broker.service.QueuedTaskAssigner +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.property.PropertySerializerFactory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapMerge +import kotlinx.coroutines.flow.map +import org.koin.core.annotation.Singleton +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@Singleton +class AssignmentTaskService( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + private val queuedTaskAssigner: QueuedTaskAssigner, + private val propertySerializerFactory: PropertySerializerFactory +) : + AssignmentTaskServiceCoroutineImplBase(coroutineContext) { + + override fun ready(requests: Flow): Flow { + return requests + .flatMapMerge { + queuedTaskAssigner.ready(it.consumerId.toUUID(), it.numberOfConcurrent) + } + .map { + TaskRequest + .newBuilder() + .setName(it.task.name) + .setId(it.task.id.toUUID()) + .setAttempt(it.attempt) + .setQueuedAt(it.queuedAt.toTimestamp()) + .putAllProperties(PropertySerializeUtils.serialize(propertySerializerFactory, it.task.properties)) + .build() + } + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/DefinitionTaskService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/DefinitionTaskService.kt new file mode 100644 index 00000000..3ce2b5b0 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/DefinitionTaskService.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.interfaces.grpc + +import com.google.protobuf.Empty +import dev.usbharu.owl.DefinitionTask +import dev.usbharu.owl.DefinitionTask.TaskDefined +import dev.usbharu.owl.DefinitionTaskServiceGrpcKt.DefinitionTaskServiceCoroutineImplBase +import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinition +import dev.usbharu.owl.broker.service.RegisterTaskService +import org.koin.core.annotation.Singleton +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@Singleton +class DefinitionTaskService(coroutineContext: CoroutineContext = EmptyCoroutineContext,private val registerTaskService: RegisterTaskService) : + DefinitionTaskServiceCoroutineImplBase(coroutineContext) { + override suspend fun register(request: DefinitionTask.TaskDefinition): TaskDefined { + registerTaskService.registerTask( + TaskDefinition( + request.name, + request.priority, + request.maxRetry, + request.timeoutMilli, + request.propertyDefinitionHash, + request.retryPolicy + ) + ) + return TaskDefined + .newBuilder() + .setTaskId( + request.name + ) + .build() + } + + override suspend fun unregister(request: DefinitionTask.TaskUnregister): Empty { + registerTaskService.unregisterTask(request.name) + return Empty.getDefaultInstance() + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/ProducerService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/ProducerService.kt new file mode 100644 index 00000000..c01bec69 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/ProducerService.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.interfaces.grpc + +import dev.usbharu.owl.ProducerOuterClass +import dev.usbharu.owl.ProducerServiceGrpcKt.ProducerServiceCoroutineImplBase +import dev.usbharu.owl.broker.external.toUUID +import dev.usbharu.owl.broker.service.ProducerService +import dev.usbharu.owl.broker.service.RegisterProducerRequest +import org.koin.core.annotation.Singleton +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@Singleton +class ProducerService( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + private val producerService: ProducerService +) : + ProducerServiceCoroutineImplBase(coroutineContext) { + override suspend fun registerProducer(request: ProducerOuterClass.Producer): ProducerOuterClass.RegisterProducerResponse { + val registerProducer = producerService.registerProducer( + RegisterProducerRequest( + request.name, request.hostname + ) + ) + return ProducerOuterClass.RegisterProducerResponse.newBuilder().setId(registerProducer.toUUID()).build() + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/SubscribeTaskService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/SubscribeTaskService.kt new file mode 100644 index 00000000..f521ca0c --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/SubscribeTaskService.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.interfaces.grpc + +import dev.usbharu.owl.Consumer +import dev.usbharu.owl.SubscribeTaskServiceGrpcKt.SubscribeTaskServiceCoroutineImplBase +import dev.usbharu.owl.broker.external.toUUID +import dev.usbharu.owl.broker.service.ConsumerService +import dev.usbharu.owl.broker.service.RegisterConsumerRequest +import org.koin.core.annotation.Singleton +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@Singleton +class SubscribeTaskService( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + private val consumerService: ConsumerService +) : + SubscribeTaskServiceCoroutineImplBase(coroutineContext) { + override suspend fun subscribeTask(request: Consumer.SubscribeTaskRequest): Consumer.SubscribeTaskResponse { + val id = + consumerService.registerConsumer(RegisterConsumerRequest(request.name, request.hostname, request.tasksList)) + return Consumer.SubscribeTaskResponse.newBuilder().setId(id.toUUID()).build() + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskPublishService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskPublishService.kt new file mode 100644 index 00000000..e305cb41 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskPublishService.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.interfaces.grpc + +import dev.usbharu.owl.PublishTaskOuterClass +import dev.usbharu.owl.PublishTaskOuterClass.PublishedTask +import dev.usbharu.owl.PublishTaskOuterClass.PublishedTasks +import dev.usbharu.owl.TaskPublishServiceGrpcKt.TaskPublishServiceCoroutineImplBase +import dev.usbharu.owl.broker.external.toUUID +import dev.usbharu.owl.broker.service.PublishTask +import dev.usbharu.owl.broker.service.TaskPublishService +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.property.PropertySerializerFactory +import io.grpc.Status +import io.grpc.StatusException +import org.koin.core.annotation.Singleton +import org.slf4j.LoggerFactory +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@Singleton +class TaskPublishService( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + private val taskPublishService: TaskPublishService, + private val propertySerializerFactory: PropertySerializerFactory +) : + TaskPublishServiceCoroutineImplBase(coroutineContext) { + + override suspend fun publishTask(request: PublishTaskOuterClass.PublishTask): PublishedTask { + + logger.warn("aaaaaaaaaaa") + + + + return try { + + val publishedTask = taskPublishService.publishTask( + PublishTask( + request.name, + request.producerId.toUUID(), + PropertySerializeUtils.deserialize(propertySerializerFactory, request.propertiesMap) + ) + ) + PublishedTask.newBuilder().setName(publishedTask.name).setId(publishedTask.id.toUUID()).build() + } catch (e: Throwable) { + logger.warn("exception ", e) + throw StatusException(Status.INTERNAL) + } + } + + override suspend fun publishTasks(request: PublishTaskOuterClass.PublishTasks): PublishTaskOuterClass.PublishedTasks { + + val tasks = request.propertiesArrayList.map { + PublishTask( + request.name, + request.producerId.toUUID(), + PropertySerializeUtils.deserialize(propertySerializerFactory, it.propertiesMap) + ) + } + + val publishTasks = taskPublishService.publishTasks(tasks) + + return PublishedTasks.newBuilder().setName(request.name).addAllId(publishTasks.map { it.id.toUUID() }).build() + } + + companion object { + private val logger = + LoggerFactory.getLogger(dev.usbharu.owl.broker.interfaces.grpc.TaskPublishService::class.java) + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultService.kt new file mode 100644 index 00000000..613480b9 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultService.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.interfaces.grpc + +import com.google.protobuf.Empty +import dev.usbharu.owl.TaskResultOuterClass +import dev.usbharu.owl.TaskResultServiceGrpcKt +import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult +import dev.usbharu.owl.broker.external.toUUID +import dev.usbharu.owl.broker.service.TaskManagementService +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.property.PropertySerializerFactory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach +import java.util.* +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +class TaskResultService( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + private val taskManagementService: TaskManagementService, + private val propertySerializerFactory: PropertySerializerFactory +) : + TaskResultServiceGrpcKt.TaskResultServiceCoroutineImplBase(coroutineContext) { + override suspend fun tasKResult(requests: Flow): Empty { + requests.onEach { + taskManagementService.queueProcessed( + TaskResult( + id = UUID.randomUUID(), + taskId = it.id.toUUID(), + success = it.success, + attempt = it.attempt, + result = PropertySerializeUtils.deserialize(propertySerializerFactory, it.resultMap), + message = it.message + ) + ) + }.collect() + return Empty.getDefaultInstance() + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultSubscribeService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultSubscribeService.kt new file mode 100644 index 00000000..287dc449 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultSubscribeService.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.interfaces.grpc + +import dev.usbharu.owl.* +import dev.usbharu.owl.broker.external.toUUID +import dev.usbharu.owl.broker.service.TaskManagementService +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.property.PropertySerializerFactory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import org.koin.core.annotation.Singleton +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@Singleton +class TaskResultSubscribeService( + private val taskManagementService: TaskManagementService, + private val propertySerializerFactory: PropertySerializerFactory, + coroutineContext: CoroutineContext = EmptyCoroutineContext +) : + TaskResultSubscribeServiceGrpcKt.TaskResultSubscribeServiceCoroutineImplBase(coroutineContext) { + override fun subscribe(request: Uuid.UUID): Flow { + return taskManagementService + .subscribeResult(request.toUUID()) + .map { + taskResults { + id = it.id.toUUID() + name = it.name + attempt = it.attempt + success = it.success + results.addAll(it.results.map { + taskResult { + id = it.taskId.toUUID() + success = it.success + attempt = it.attempt + result.putAll(PropertySerializeUtils.serialize(propertySerializerFactory, it.result)) + message = it.message + } + }) + } + } + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/AssignQueuedTaskDecider.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/AssignQueuedTaskDecider.kt new file mode 100644 index 00000000..e8ff8b41 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/AssignQueuedTaskDecider.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.exception.repository.RecordNotFoundException +import dev.usbharu.owl.broker.domain.model.consumer.ConsumerRepository +import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.take +import org.koin.core.annotation.Singleton +import java.util.* +interface AssignQueuedTaskDecider { + fun findAssignableQueue(consumerId: UUID, numberOfConcurrent: Int): Flow +} +@Singleton +class AssignQueuedTaskDeciderImpl( + private val consumerRepository: ConsumerRepository, + private val queueStore: QueueStore +) : AssignQueuedTaskDecider { + override fun findAssignableQueue(consumerId: UUID, numberOfConcurrent: Int): Flow { + return flow { + val consumer = consumerRepository.findById(consumerId) + ?: throw RecordNotFoundException("Consumer not found. id: $consumerId") + emitAll( + queueStore.findByTaskNameInAndIsActiveIsTrueAndOrderByPriority( + consumer.tasks, + numberOfConcurrent + ).take(numberOfConcurrent) + ) + } + + } + +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/ConsumerService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/ConsumerService.kt new file mode 100644 index 00000000..62bb6df3 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/ConsumerService.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.model.consumer.Consumer +import dev.usbharu.owl.broker.domain.model.consumer.ConsumerRepository +import org.koin.core.annotation.Single +import org.koin.core.annotation.Singleton +import org.slf4j.LoggerFactory +import java.util.* + +interface ConsumerService { + suspend fun registerConsumer(registerConsumerRequest: RegisterConsumerRequest): UUID +} + +@Singleton +class ConsumerServiceImpl(private val consumerRepository: ConsumerRepository) : ConsumerService { + override suspend fun registerConsumer(registerConsumerRequest: RegisterConsumerRequest): UUID { + val id = UUID.randomUUID() + + consumerRepository.save( + Consumer( + id, + registerConsumerRequest.name, + registerConsumerRequest.hostname, + registerConsumerRequest.tasks + ) + ) + + logger.info( + "Register a new Consumer. name: {} hostname: {} tasks: {}", + registerConsumerRequest.name, + registerConsumerRequest.hostname, + registerConsumerRequest.tasks.size + ) + + return id + } + + companion object { + private val logger = LoggerFactory.getLogger(ConsumerServiceImpl::class.java) + } +} + +data class RegisterConsumerRequest( + val name: String, + val hostname: String, + val tasks: List +) \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/DefaultPropertySerializerFactory.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/DefaultPropertySerializerFactory.kt new file mode 100644 index 00000000..d35c6e06 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/DefaultPropertySerializerFactory.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.common.property.* +import org.koin.core.annotation.Singleton + +@Singleton(binds = [PropertySerializerFactory::class]) +class DefaultPropertySerializerFactory : + CustomPropertySerializerFactory( + setOf( + IntegerPropertySerializer(), + StringPropertyValueSerializer(), + DoublePropertySerializer(), + BooleanPropertySerializer() + ) + ) \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/ProducerService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/ProducerService.kt new file mode 100644 index 00000000..1c803a02 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/ProducerService.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.model.producer.Producer +import dev.usbharu.owl.broker.domain.model.producer.ProducerRepository +import org.koin.core.annotation.Singleton +import org.slf4j.LoggerFactory +import java.time.Instant +import java.util.* + +interface ProducerService { + suspend fun registerProducer(producer: RegisterProducerRequest):UUID +} + +@Singleton +class ProducerServiceImpl(private val producerRepository: ProducerRepository) : ProducerService { + override suspend fun registerProducer(producer: RegisterProducerRequest): UUID { + + val id = UUID.randomUUID() + + val saveProducer = Producer( + id = id, + name = producer.name, + hostname = producer.hostname, + registeredTask = emptyList(), + createdAt = Instant.now() + ) + + producerRepository.save(saveProducer) + + logger.info("Register a new Producer. name: {} hostname: {}",saveProducer.name,saveProducer.hostname) + return id + } + + companion object{ + private val logger = LoggerFactory.getLogger(ProducerServiceImpl::class.java) + } +} + +data class RegisterProducerRequest( + val name: String, + val hostname: String +) \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueScanner.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueScanner.kt new file mode 100644 index 00000000..5102571a --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueScanner.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.isActive +import org.koin.core.annotation.Singleton +import java.time.Instant + +interface QueueScanner { + fun startScan(): Flow +} + +@Singleton +class QueueScannerImpl(private val queueStore: QueueStore) : QueueScanner { + override fun startScan(): Flow { + return flow { + while (currentCoroutineContext().isActive) { + emitAll(scanQueue()) + delay(1000) + } + } + } + + private fun scanQueue(): Flow { + return queueStore.findByQueuedAtBeforeAndIsActiveIsTrue(Instant.now().minusSeconds(10)) + } + +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueStore.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueStore.kt new file mode 100644 index 00000000..2630915a --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueStore.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask +import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTaskRepository +import kotlinx.coroutines.flow.Flow +import org.koin.core.annotation.Singleton +import java.time.Instant + +interface QueueStore { + suspend fun enqueue(queuedTask: QueuedTask) + suspend fun enqueueAll(queuedTaskList: List) + + suspend fun dequeue(queuedTask: QueuedTask) + suspend fun dequeueAll(queuedTaskList: List) + fun findByTaskNameInAndIsActiveIsTrueAndOrderByPriority(tasks: List, limit: Int): Flow + + fun findByQueuedAtBeforeAndIsActiveIsTrue(instant: Instant): Flow +} + +@Singleton +class QueueStoreImpl(private val queuedTaskRepository: QueuedTaskRepository) : QueueStore { + override suspend fun enqueue(queuedTask: QueuedTask) { + queuedTaskRepository.save(queuedTask) + } + + override suspend fun enqueueAll(queuedTaskList: List) { + queuedTaskList.forEach { enqueue(it) } + } + + override suspend fun dequeue(queuedTask: QueuedTask) { + queuedTaskRepository.findByTaskIdAndAssignedConsumerIsNullAndUpdate(queuedTask.task.id, queuedTask) + } + + override suspend fun dequeueAll(queuedTaskList: List) { + return queuedTaskList.forEach { dequeue(it) } + } + + override fun findByTaskNameInAndIsActiveIsTrueAndOrderByPriority( + tasks: List, + limit: Int + ): Flow { + return queuedTaskRepository.findByTaskNameInAndIsActiveIsTrueAndOrderByPriority(tasks, limit) + } + + override fun findByQueuedAtBeforeAndIsActiveIsTrue(instant: Instant): Flow { + return queuedTaskRepository.findByQueuedAtBeforeAndIsActiveIsTrue(instant) + } + +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueuedTaskAssigner.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueuedTaskAssigner.kt new file mode 100644 index 00000000..da6c1390 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueuedTaskAssigner.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.exception.service.QueueCannotDequeueException +import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach +import org.koin.core.annotation.Singleton +import org.slf4j.LoggerFactory +import java.time.Instant +import java.util.* + +interface QueuedTaskAssigner { + fun ready(consumerId: UUID, numberOfConcurrent: Int): Flow +} + +@Singleton +class QueuedTaskAssignerImpl( + private val taskManagementService: TaskManagementService, + private val queueStore: QueueStore +) : QueuedTaskAssigner { + override fun ready(consumerId: UUID, numberOfConcurrent: Int): Flow { + return flow { + taskManagementService.findAssignableTask(consumerId, numberOfConcurrent) + .onEach { + val assignTask = assignTask(it, consumerId) + + if (assignTask != null) { + emit(assignTask) + } + } + .collect() + } + } + + private suspend fun assignTask(queuedTask: QueuedTask, consumerId: UUID): QueuedTask? { + return try { + + val assignedTaskQueue = + queuedTask.copy(assignedConsumer = consumerId, assignedAt = Instant.now(), isActive = false) + logger.trace( + "Try assign task: {} id: {} consumer: {}", + queuedTask.task.name, + queuedTask.task.id, + consumerId + ) + + queueStore.dequeue(assignedTaskQueue) + + logger.debug( + "Assign Task. name: {} id: {} attempt: {} consumer: {}", + queuedTask.task.name, + queuedTask.task.id, + queuedTask.attempt, + queuedTask.assignedConsumer + ) + assignedTaskQueue + } catch (e: QueueCannotDequeueException) { + logger.debug("Failed dequeue queue", e) + return null + } + } + + companion object { + private val logger = LoggerFactory.getLogger(QueuedTaskAssignerImpl::class.java) + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/RegisterTaskService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/RegisterTaskService.kt new file mode 100644 index 00000000..32436a14 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/RegisterTaskService.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.exception.service.IncompatibleTaskException +import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinition +import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinitionRepository +import org.koin.core.annotation.Singleton +import org.slf4j.LoggerFactory + +interface RegisterTaskService { + suspend fun registerTask(taskDefinition: TaskDefinition) + + suspend fun unregisterTask(name:String) +} + +@Singleton +class RegisterTaskServiceImpl(private val taskDefinitionRepository: TaskDefinitionRepository) : RegisterTaskService { + override suspend fun registerTask(taskDefinition: TaskDefinition) { + val definedTask = taskDefinitionRepository.findByName(taskDefinition.name) + if (definedTask != null) { + logger.debug("Task already defined. name: ${taskDefinition.name}") + if (taskDefinition.propertyDefinitionHash != definedTask.propertyDefinitionHash) { + throw IncompatibleTaskException("Task ${taskDefinition.name} has already been defined, and the parameters are incompatible.") + } + return + } + taskDefinitionRepository.save(taskDefinition) + + logger.info("Register a new task. name: {}",taskDefinition.name) + } + + // todo すでにpublish済みのタスクをどうするか決めさせる + override suspend fun unregisterTask(name: String) { + taskDefinitionRepository.deleteByName(name) + + logger.info("Unregister a task. name: {}",name) + } + + companion object{ + private val logger = LoggerFactory.getLogger(RegisterTaskServiceImpl::class.java) + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/RetryPolicyFactory.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/RetryPolicyFactory.kt new file mode 100644 index 00000000..58df5d64 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/RetryPolicyFactory.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.exception.service.RetryPolicyNotFoundException +import dev.usbharu.owl.common.retry.RetryPolicy +import org.slf4j.LoggerFactory + +interface RetryPolicyFactory { + fun factory(name: String): RetryPolicy +} + +class DefaultRetryPolicyFactory(private val map: Map) : RetryPolicyFactory { + override fun factory(name: String): RetryPolicy { + return map[name] ?: throwException(name) + } + + private fun throwException(name: String): Nothing { + logger.warn("RetryPolicy not found. name: {}", name) + throw RetryPolicyNotFoundException("RetryPolicy not found. name: $name") + } + + companion object { + private val logger = LoggerFactory.getLogger(RetryPolicyFactory::class.java) + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskManagementService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskManagementService.kt new file mode 100644 index 00000000..066dafa3 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskManagementService.kt @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.exception.repository.RecordNotFoundException +import dev.usbharu.owl.broker.domain.exception.service.TaskNotRegisterException +import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask +import dev.usbharu.owl.broker.domain.model.task.Task +import dev.usbharu.owl.broker.domain.model.task.TaskRepository +import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinitionRepository +import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult +import dev.usbharu.owl.broker.domain.model.taskresult.TaskResultRepository +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import org.koin.core.annotation.Singleton +import org.slf4j.LoggerFactory +import java.time.Instant +import java.util.* + + +interface TaskManagementService { + + suspend fun startManagement(coroutineScope: CoroutineScope) + fun findAssignableTask(consumerId: UUID, numberOfConcurrent: Int): Flow + + suspend fun queueProcessed(taskResult: TaskResult) + + fun subscribeResult(producerId: UUID): Flow +} + +@Singleton +class TaskManagementServiceImpl( + private val taskScanner: TaskScanner, + private val queueStore: QueueStore, + private val taskDefinitionRepository: TaskDefinitionRepository, + private val assignQueuedTaskDecider: AssignQueuedTaskDecider, + private val retryPolicyFactory: RetryPolicyFactory, + private val taskRepository: TaskRepository, + private val queueScanner: QueueScanner, + private val taskResultRepository: TaskResultRepository +) : TaskManagementService { + + private var taskFlow: Flow = flowOf() + private var queueFlow: Flow = flowOf() + override suspend fun startManagement(coroutineScope: CoroutineScope) { + taskFlow = taskScanner.startScan() + queueFlow = queueScanner.startScan() + + coroutineScope { + listOf( + launch { + taskFlow.onEach { + enqueueTask(it) + }.collect() + }, + launch { + queueFlow.onEach { + timeoutQueue(it) + }.collect() + } + ).joinAll() + } + } + + + override fun findAssignableTask(consumerId: UUID, numberOfConcurrent: Int): Flow { + return assignQueuedTaskDecider.findAssignableQueue(consumerId, numberOfConcurrent) + } + + private suspend fun enqueueTask(task: Task): QueuedTask { + + val definedTask = taskDefinitionRepository.findByName(task.name) + ?: throw TaskNotRegisterException("Task ${task.name} not definition.") + + val queuedTask = QueuedTask( + attempt = task.attempt + 1, + queuedAt = Instant.now(), + task = task, + priority = definedTask.priority, + isActive = true, + timeoutAt = null, + assignedConsumer = null, + assignedAt = null + ) + + val copy = task.copy( + nextRetry = retryPolicyFactory.factory(definedTask.retryPolicy) + .nextRetry(Instant.now(), queuedTask.attempt) + ) + + taskRepository.save(copy) + + queueStore.enqueue(queuedTask) + logger.debug("Enqueue Task. name: {} id: {} attempt: {}", task.name, task.id, queuedTask.attempt) + return queuedTask + } + + private suspend fun timeoutQueue(queuedTask: QueuedTask) { + val timeoutQueue = queuedTask.copy(isActive = false, timeoutAt = Instant.now()) + + queueStore.dequeue(timeoutQueue) + + + val task = taskRepository.findById(timeoutQueue.task.id) + ?: throw RecordNotFoundException("Task not found. id: ${timeoutQueue.task.id}") + val copy = task.copy(attempt = timeoutQueue.attempt) + + logger.warn( + "Queue timed out. name: {} id: {} attempt: {}", + timeoutQueue.task.name, + timeoutQueue.task.id, + timeoutQueue.attempt + ) + taskRepository.save(copy) + } + + override suspend fun queueProcessed(taskResult: TaskResult) { + val task = taskRepository.findById(taskResult.id) + ?: throw RecordNotFoundException("Task not found. id: ${taskResult.id}") + + val taskDefinition = taskDefinitionRepository.findByName(task.name) + ?: throw TaskNotRegisterException("Task ${task.name} not definition.") + + val completedAt = if (taskResult.success) { + Instant.now() + } else if (taskResult.attempt >= taskDefinition.maxRetry) { + Instant.now() + } else { + null + } + + taskResultRepository.save(taskResult) + + taskRepository.findByIdAndUpdate( + taskResult.id, + task.copy(completedAt = completedAt, attempt = taskResult.attempt) + ) + + } + + override fun subscribeResult(producerId: UUID): Flow { + return flow { + + while (currentCoroutineContext().isActive) { + taskRepository + .findByPublishProducerIdAndCompletedAtIsNotNull(producerId) + .onEach { + val results = taskResultRepository.findByTaskId(it.id).toList() + emit( + TaskResults( + it.name, + it.id, + results.any { it.success }, + it.attempt, + results + ) + ) + } + delay(500) + } + + } + + } + + companion object { + private val logger = LoggerFactory.getLogger(TaskManagementServiceImpl::class.java) + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskPublishService.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskPublishService.kt new file mode 100644 index 00000000..8446bf48 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskPublishService.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.exception.service.TaskNotRegisterException +import dev.usbharu.owl.broker.domain.model.task.Task +import dev.usbharu.owl.broker.domain.model.task.TaskRepository +import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinitionRepository +import dev.usbharu.owl.common.property.PropertyValue +import org.koin.core.annotation.Singleton +import org.slf4j.LoggerFactory +import java.time.Instant +import java.util.* + +interface TaskPublishService { + suspend fun publishTask(publishTask: PublishTask): PublishedTask + suspend fun publishTasks(list: List): List +} + +data class PublishTask( + val name: String, + val producerId: UUID, + val properties: Map> +) + +data class PublishedTask( + val name: String, + val id: UUID +) + +@Singleton +class TaskPublishServiceImpl( + private val taskRepository: TaskRepository, + private val taskDefinitionRepository: TaskDefinitionRepository, + private val retryPolicyFactory: RetryPolicyFactory +) : TaskPublishService { + override suspend fun publishTask(publishTask: PublishTask): PublishedTask { + val id = UUID.randomUUID() + + val definition = taskDefinitionRepository.findByName(publishTask.name) + ?: throw TaskNotRegisterException("Task ${publishTask.name} not definition.") + + val published = Instant.now() + val nextRetry = retryPolicyFactory.factory(definition.retryPolicy).nextRetry(published, 0) + + val task = Task( + name = publishTask.name, + id = id, + publishProducerId = publishTask.producerId, + publishedAt = published, + completedAt = null, + attempt = 0, + properties = publishTask.properties, + nextRetry = nextRetry + ) + + taskRepository.save(task) + + logger.debug("Published task #{} name: {}", task.id, task.name) + + return PublishedTask( + name = publishTask.name, + id = id + ) + } + + override suspend fun publishTasks(list: List): List { + + val first = list.first() + + val definition = taskDefinitionRepository.findByName(first.name) + ?: throw TaskNotRegisterException("Task ${first.name} not definition.") + + val published = Instant.now() + + val nextRetry = retryPolicyFactory.factory(definition.retryPolicy).nextRetry(published, 0) + + val tasks = list.map { + Task( + it.name, + UUID.randomUUID(), + first.producerId, + published, + nextRetry, + null, + 0, + it.properties + ) + } + + taskRepository.saveAll(tasks) + + logger.debug("Published {} tasks. name: {}", tasks.size, first.name) + + return tasks.map { PublishedTask(it.name, it.id) } + } + + companion object { + private val logger = LoggerFactory.getLogger(TaskPublishServiceImpl::class.java) + } +} \ No newline at end of file diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskResults.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskResults.kt new file mode 100644 index 00000000..a61dbb0c --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskResults.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult +import java.util.* + +data class TaskResults( + val name:String, + val id:UUID, + val success:Boolean, + val attempt:Int, + val results: List +) diff --git a/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskScanner.kt b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskScanner.kt new file mode 100644 index 00000000..3204f409 --- /dev/null +++ b/owl/broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskScanner.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.broker.service + +import dev.usbharu.owl.broker.domain.model.task.Task +import dev.usbharu.owl.broker.domain.model.task.TaskRepository +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.isActive +import org.koin.core.annotation.Singleton +import org.slf4j.LoggerFactory +import java.time.Instant + +interface TaskScanner { + + fun startScan(): Flow +} + +@Singleton +class TaskScannerImpl(private val taskRepository: TaskRepository) : + TaskScanner { + + override fun startScan(): Flow = flow { + while (currentCoroutineContext().isActive) { + emitAll(scanTask()) + delay(500) + } + } + + private fun scanTask(): Flow { + return taskRepository.findByNextRetryBeforeAndCompletedAtIsNull(Instant.now()) + } + + companion object { + private val logger = LoggerFactory.getLogger(TaskScannerImpl::class.java) + } +} \ No newline at end of file diff --git a/owl/broker/src/main/proto/consumer.proto b/owl/broker/src/main/proto/consumer.proto new file mode 100644 index 00000000..252d4a27 --- /dev/null +++ b/owl/broker/src/main/proto/consumer.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +import "uuid.proto"; + +option java_package = "dev.usbharu.owl"; + +message SubscribeTaskRequest { + string name = 1; + string hostname = 2; + repeated string tasks = 3;; +} + +message SubscribeTaskResponse { + UUID id = 1; +} + +service SubscribeTaskService { + rpc SubscribeTask (SubscribeTaskRequest) returns (SubscribeTaskResponse); +} \ No newline at end of file diff --git a/owl/broker/src/main/proto/definition_task.proto b/owl/broker/src/main/proto/definition_task.proto new file mode 100644 index 00000000..6cab5257 --- /dev/null +++ b/owl/broker/src/main/proto/definition_task.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +option java_package = "dev.usbharu.owl"; + +import "google/protobuf/empty.proto"; +import "uuid.proto"; + + +message TaskDefinition { + string name = 1; + int32 priority = 2; + int32 max_retry = 3; + int64 timeout_milli = 4; + int64 property_definition_hash = 5; + UUID producer_id = 6; + string retryPolicy = 7; +} + +message TaskDefined { + string task_id = 1; +} + +message TaskUnregister { + string name = 1; + UUID producer_id = 2; +} + +service DefinitionTaskService { + rpc register(TaskDefinition) returns (TaskDefined); + rpc unregister(TaskUnregister) returns (google.protobuf.Empty); +} \ No newline at end of file diff --git a/owl/broker/src/main/proto/producer.proto b/owl/broker/src/main/proto/producer.proto new file mode 100644 index 00000000..b4bbcd7a --- /dev/null +++ b/owl/broker/src/main/proto/producer.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +import "uuid.proto"; + +option java_package = "dev.usbharu.owl"; + +message Producer { + string name = 1; + string hostname = 2; +} + +message RegisterProducerResponse { + UUID id = 1; +} + +service ProducerService { + rpc registerProducer (Producer) returns (RegisterProducerResponse); +} \ No newline at end of file diff --git a/owl/broker/src/main/proto/property.proto b/owl/broker/src/main/proto/property.proto new file mode 100644 index 00000000..138e7e22 --- /dev/null +++ b/owl/broker/src/main/proto/property.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; + +option java_package = "dev.usbharu.owl"; + +message Property{ + oneof value { + google.protobuf.Empty empty = 1; + string string = 2; + int32 integer = 3; + } +} \ No newline at end of file diff --git a/owl/broker/src/main/proto/publish_task.proto b/owl/broker/src/main/proto/publish_task.proto new file mode 100644 index 00000000..620e6396 --- /dev/null +++ b/owl/broker/src/main/proto/publish_task.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +import "uuid.proto"; + +option java_package = "dev.usbharu.owl"; + + +message PublishTask { + string name = 1; + google.protobuf.Timestamp publishedAt = 2; + map properties = 3; + UUID producer_id = 4; +} + +message Properties { + map properties = 1; +} + +message PublishTasks { + string name = 1; + google.protobuf.Timestamp publishedAt = 2; + repeated Properties propertiesArray = 3; + UUID producer_id = 4; +} + +message PublishedTask { + string name = 1; + UUID id = 2; +} + +message PublishedTasks { + string name = 1; + repeated UUID id = 2; +} + +service TaskPublishService { + rpc publishTask (PublishTask) returns (PublishedTask); + rpc publishTasks(PublishTasks) returns (PublishedTasks); +} diff --git a/owl/broker/src/main/proto/task.proto b/owl/broker/src/main/proto/task.proto new file mode 100644 index 00000000..48566fdb --- /dev/null +++ b/owl/broker/src/main/proto/task.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; +import "uuid.proto"; +import "google/protobuf/timestamp.proto"; +import "property.proto"; + +option java_package = "dev.usbharu.owl"; + +message ReadyRequest { + int32 number_of_concurrent = 1; + UUID consumer_id = 2; +} + +message TaskRequest { + string name = 1; + UUID id = 2; + int32 attempt = 4; + google.protobuf.Timestamp queuedAt = 5; + map properties = 6; +} + +service AssignmentTaskService { + rpc ready (stream ReadyRequest) returns (stream TaskRequest); +} \ No newline at end of file diff --git a/owl/broker/src/main/proto/task_result.proto b/owl/broker/src/main/proto/task_result.proto new file mode 100644 index 00000000..642f7425 --- /dev/null +++ b/owl/broker/src/main/proto/task_result.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +import "uuid.proto"; +import "google/protobuf/empty.proto"; +import "property.proto"; + +option java_package = "dev.usbharu.owl"; + +message TaskResult { + UUID id = 1; + bool success = 2; + int32 attempt = 3; + map result = 4; + string message = 5; +} + +service TaskResultService{ + rpc tasKResult(stream TaskResult) returns (google.protobuf.Empty); +} \ No newline at end of file diff --git a/owl/broker/src/main/proto/task_result_producer.proto b/owl/broker/src/main/proto/task_result_producer.proto new file mode 100644 index 00000000..6102a020 --- /dev/null +++ b/owl/broker/src/main/proto/task_result_producer.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +import "uuid.proto"; +import "task_result.proto"; + +option java_package = "dev.usbharu.owl"; + +message TaskResults { + string name = 1; + UUID id = 2; + bool success = 3; + int32 attempt = 4; + repeated TaskResult results = 5; +} + +service TaskResultSubscribeService { + rpc subscribe(UUID) returns (stream TaskResults); +} \ No newline at end of file diff --git a/owl/broker/src/main/proto/uuid.proto b/owl/broker/src/main/proto/uuid.proto new file mode 100644 index 00000000..26d61001 --- /dev/null +++ b/owl/broker/src/main/proto/uuid.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "dev.usbharu.owl"; + +message UUID { + uint64 most_significant_uuid_bits = 1; + uint64 least_significant_uuid_bits = 2; +} \ No newline at end of file diff --git a/owl/broker/src/main/resources/log4j2.xml b/owl/broker/src/main/resources/log4j2.xml new file mode 100644 index 00000000..31bfafec --- /dev/null +++ b/owl/broker/src/main/resources/log4j2.xml @@ -0,0 +1,40 @@ + + + + + + + %d{yyyy/MM/dd HH:mm:ss.SSS} [%t] %-6p %c{10} | %m%n + + + + + ${format1} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/owl/build.gradle.kts b/owl/build.gradle.kts new file mode 100644 index 00000000..35030563 --- /dev/null +++ b/owl/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + kotlin("jvm") version "1.9.22" +} + + +allprojects { + group = "dev.usbharu" + version = "0.0.1" + + + repositories { + mavenCentral() + } +} + +subprojects { + apply { + plugin("org.jetbrains.kotlin.jvm") + } + kotlin { + jvmToolchain(17) + } + + dependencies { + implementation("org.slf4j:slf4j-api:2.0.12") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") + + + } + + + tasks.test { + useJUnitPlatform() + } + + +} \ No newline at end of file diff --git a/owl/common/build.gradle.kts b/owl/common/build.gradle.kts new file mode 100644 index 00000000..58cc5412 --- /dev/null +++ b/owl/common/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + kotlin("jvm") +} + +group = "dev.usbharu" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test") +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(17) +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/BooleanPropertyValue.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/BooleanPropertyValue.kt new file mode 100644 index 00000000..b595f31c --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/BooleanPropertyValue.kt @@ -0,0 +1,33 @@ +package dev.usbharu.owl.common.property + +/** + * Boolean型のプロパティ + * + * @property value プロパティ + */ +class BooleanPropertyValue(override val value: Boolean) : PropertyValue() { + override val type: PropertyType + get() = PropertyType.binary +} + +/** + * [BooleanPropertyValue]のシリアライザー + * + */ +class BooleanPropertySerializer : PropertySerializer { + override fun isSupported(propertyValue: PropertyValue<*>): Boolean { + return propertyValue.value is Boolean + } + + override fun isSupported(string: String): Boolean { + return string.startsWith("bool:") + } + + override fun serialize(propertyValue: PropertyValue<*>): String { + return "bool:" + propertyValue.value.toString() + } + + override fun deserialize(string: String): PropertyValue { + return BooleanPropertyValue(string.replace("bool:", "").toBoolean()) + } +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/CustomPropertySerializerFactory.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/CustomPropertySerializerFactory.kt new file mode 100644 index 00000000..c1d0537b --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/CustomPropertySerializerFactory.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.property + +/** + * [Set]でカスタマイズできる[PropertySerializerFactory] + * + * @property propertySerializers [PropertySerializer]の[Set] + */ +open class CustomPropertySerializerFactory(private val propertySerializers: Set>) : + PropertySerializerFactory { + override fun factory(propertyValue: PropertyValue): PropertySerializer { + return propertySerializers.first { it.isSupported(propertyValue) } as PropertySerializer + } + + override fun factory(string: String): PropertySerializer<*> { + return propertySerializers.first { it.isSupported(string) } + } +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/DoublePropertyValue.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/DoublePropertyValue.kt new file mode 100644 index 00000000..c201cbaa --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/DoublePropertyValue.kt @@ -0,0 +1,33 @@ +package dev.usbharu.owl.common.property + +/** + * Double型のプロパティ + * + * @property value プロパティ + */ +class DoublePropertyValue(override val value: Double) : PropertyValue() { + override val type: PropertyType + get() = PropertyType.number +} + +/** + * [DoublePropertyValue]のシリアライザー + * + */ +class DoublePropertySerializer : PropertySerializer { + override fun isSupported(propertyValue: PropertyValue<*>): Boolean { + return propertyValue.value is Double + } + + override fun isSupported(string: String): Boolean { + return string.startsWith("double:") + } + + override fun serialize(propertyValue: PropertyValue<*>): String { + return "double:" + propertyValue.value.toString() + } + + override fun deserialize(string: String): PropertyValue { + return DoublePropertyValue(string.replace("double:", "").toDouble()) + } +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/IntegerPropertyValue.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/IntegerPropertyValue.kt new file mode 100644 index 00000000..9b49c39c --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/IntegerPropertyValue.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.property + +/** + * Integer型のプロパティ + * + * @property value プロパティ + */ +class IntegerPropertyValue(override val value: Int) : PropertyValue() { + override val type: PropertyType + get() = PropertyType.number +} + +/** + * [IntegerPropertyValue]のシリアライザー + * + */ +class IntegerPropertySerializer : PropertySerializer { + override fun isSupported(propertyValue: PropertyValue<*>): Boolean { + return propertyValue.value is Int + } + + override fun isSupported(string: String): Boolean { + return string.startsWith("int32:") + } + + override fun serialize(propertyValue: PropertyValue<*>): String { + return "int32:" + propertyValue.value.toString() + } + + override fun deserialize(string: String): PropertyValue { + return IntegerPropertyValue(string.replace("int32:", "").toInt()) + } +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeUtils.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeUtils.kt new file mode 100644 index 00000000..248e63f2 --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeUtils.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.property + +/** + * [PropertySerializer]のユーティリティークラス + */ +object PropertySerializeUtils { + /** + * Stringと[PropertyValue]の[Map]から[PropertyValue]をシリアライズし、StringとStringの[Map]として返します + * + * @param serializerFactory シリアライズに使用する[PropertySerializerFactory] + * @param properties シリアライズする[Map] + * @return Stringとシリアライズ済みの[PropertyValue]の[Map] + */ + fun serialize( + serializerFactory: PropertySerializerFactory, + properties: Map> + ): Map = + properties.map { it.key to serializerFactory.factory(it.value).serialize(it.value) }.toMap() + + /** + * Stringとシリアライズ済みの[PropertyValue]の[Map]からシリアライズ済みの[PropertyValue]をデシリアライズし、Stringと[PropertyValue]の[Map]として返します + * + * @param serializerFactory デシリアライズに使用する[PropertySerializerFactory] + * @param properties デシリアライズする[Map] + * @return Stringと[PropertyValue]の[Map] + */ + fun deserialize( + serializerFactory: PropertySerializerFactory, + properties: Map + ): Map> = + properties.map { it.key to serializerFactory.factory(it.value).deserialize(it.value) }.toMap() +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializer.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializer.kt new file mode 100644 index 00000000..950bd9f4 --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializer.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.property + +/** + * [PropertyValue]をシリアライズ・デシリアライズします + * + * @param T [PropertyValue]の型 + */ +interface PropertySerializer { + /** + * [PropertyValue]をサポートしているかを確認します + * + * @param propertyValue 確認する[PropertyValue] + * @return サポートしている場合true + */ + fun isSupported(propertyValue: PropertyValue<*>): Boolean + + /** + * シリアライズ済みの[PropertyValue]から[PropertyValue]をサポートしているかを確認します + * + * @param string 確認するシリアライズ済みの[PropertyValue] + * @return サポートしている場合true + */ + fun isSupported(string: String): Boolean + + /** + * [PropertyValue]をシリアライズします + * + * @param propertyValue シリアライズする[PropertyValue] + * @return シリアライズ済みの[PropertyValue] + */ + fun serialize(propertyValue: PropertyValue<*>): String + + /** + * デシリアライズします + * + * @param string シリアライズ済みの[PropertyValue] + * @return デシリアライズされた[PropertyValue] + */ + fun deserialize(string: String): PropertyValue +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializerFactory.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializerFactory.kt new file mode 100644 index 00000000..9983d6cf --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializerFactory.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.property + +/** + * [PropertyValue]のシリアライザーのファクトリ + * + */ +interface PropertySerializerFactory { + /** + * [PropertyValue]からシリアライザーを作成します + * + * @param T [PropertyValue]の型 + * @param propertyValue シリアライザーを作成する[PropertyValue] + * @return 作成されたシリアライザー + */ + fun factory(propertyValue: PropertyValue): PropertySerializer + + /** + * シリアライズ済みの[PropertyValue]からシリアライザーを作成します + * + * @param string シリアライズ済みの[PropertyValue] + * @return 作成されたシリアライザー + */ + fun factory(string: String): PropertySerializer<*> +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyType.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyType.kt new file mode 100644 index 00000000..4259c62f --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyType.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.property + +/** + * プロパティの型 + * + */ +enum class PropertyType { + /** + * 数字 + * + */ + number, + + /** + * 文字列 + * + */ + string, + + /** + * バイナリ + * + */ + binary +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyValue.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyValue.kt new file mode 100644 index 00000000..c251c54f --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyValue.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.property + +/** + * プロパティで使用される値 + * + * @param T プロパティの型 + */ +sealed class PropertyValue { + /** + * プロパティ + */ + abstract val value: T + + /** + * プロパティの型 + */ + abstract val type: PropertyType +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/StringPropertyValue.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/StringPropertyValue.kt new file mode 100644 index 00000000..5b4cbaa1 --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/property/StringPropertyValue.kt @@ -0,0 +1,33 @@ +package dev.usbharu.owl.common.property + +/** + * String型のプロパティ + * + * @property value プロパティ + */ +class StringPropertyValue(override val value: String) : PropertyValue() { + override val type: PropertyType + get() = PropertyType.string +} + +/** + * [StringPropertyValue]のシリアライザー + * + */ +class StringPropertyValueSerializer : PropertySerializer { + override fun isSupported(propertyValue: PropertyValue<*>): Boolean { + return propertyValue.value is String + } + + override fun isSupported(string: String): Boolean { + return string.startsWith("str:") + } + + override fun serialize(propertyValue: PropertyValue<*>): String { + return "str:" + propertyValue.value + } + + override fun deserialize(string: String): PropertyValue { + return StringPropertyValue(string.replace("str:", "")) + } +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicy.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicy.kt new file mode 100644 index 00000000..b34cacf5 --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicy.kt @@ -0,0 +1,17 @@ +package dev.usbharu.owl.common.retry + +import java.time.Instant +import kotlin.math.pow +import kotlin.math.roundToLong + +/** + * 指数関数的に待機時間が増えるリトライポリシー + * `firstRetrySeconds x attempt ^ 2 - firstRetrySeconds` + * + * @property firstRetrySeconds + */ +class ExponentialRetryPolicy(private val firstRetrySeconds: Int = 30) : RetryPolicy { + override fun nextRetry(now: Instant, attempt: Int): Instant = + now.plusSeconds(firstRetrySeconds.times((2.0).pow(attempt).roundToLong()) - firstRetrySeconds) + +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicy.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicy.kt new file mode 100644 index 00000000..da6e4ec0 --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicy.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.retry + +import java.time.Instant + +/** + * リトライポリシー + * + */ +interface RetryPolicy { + /** + * 次のリトライ時刻を返します。 + * + * [attempt]を負の値にしてはいけません + * + * @param now 現在の時刻 + * @param attempt 試行回数 + * @return 次のリトライ時刻 + */ + fun nextRetry(now: Instant, attempt: Int): Instant +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/PropertyDefinition.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/PropertyDefinition.kt new file mode 100644 index 00000000..cbc96b0b --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/PropertyDefinition.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.task + +import dev.usbharu.owl.common.property.PropertyType + +/** + * プロパティ定義 + * + * @property map プロパティ名とプロパティタイプの[Map] + */ +class PropertyDefinition(val map: Map) : Map by map { + /** + * プロパティ定義のハッシュを求めます + * + * ハッシュ値はプロパティ名とプロパティタイプ名を結合したものを結合し、各文字のUTF-16コードと31を掛け続けたものです。 + * + * @return + */ + fun hash(): Long { + var hash = 1L + map.map { it.key + it.value.name }.joinToString("").map { hash *= it.code * 31 } + return hash + } + + +} diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/PublishedTask.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/PublishedTask.kt new file mode 100644 index 00000000..a0f944e5 --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/PublishedTask.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.task + +import java.time.Instant +import java.util.* + +/** + * 公開済みのタスク + * + * @param T タスク + * @property task タスク + * @property id タスクのID + * @property published 公開された時刻 + */ +data class PublishedTask( + val task: T, + val id: UUID, + val published: Instant +) \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/Task.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/Task.kt new file mode 100644 index 00000000..42d8dc03 --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/Task.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.task + +/** + * タスク + * + */ +open class Task { +} \ No newline at end of file diff --git a/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/TaskDefinition.kt b/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/TaskDefinition.kt new file mode 100644 index 00000000..b96c12de --- /dev/null +++ b/owl/common/src/main/kotlin/dev/usbharu/owl/common/task/TaskDefinition.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.task + +import dev.usbharu.owl.common.property.PropertyValue + +/** + * タスク定義 + * + * @param T タスク + */ +interface TaskDefinition { + /** + * タスク名 + */ + val name: String + + /** + * 優先度 + */ + val priority: Int + + /** + * 最大リトライ数 + */ + val maxRetry: Int + + /** + * リトライポリシー名 + * + * ポリシーの解決は各Brokerに依存しています + */ + val retryPolicy: String + + /** + * タスク実行時のタイムアウト(ミリ秒) + */ + val timeoutMilli: Long + + /** + * プロパティ定義 + */ + val propertyDefinition: PropertyDefinition + + /** + * [Task]の[Class] + */ + val type: Class + + /** + * タスクをシリアライズします. + * プロパティのシリアライズと混同しないようにしてください。 + * @param task シリアライズするタスク + * @return シリアライズされたタスク + */ + fun serialize(task: T): Map> + + /** + * タスクをデシリアライズします。 + * プロパティのデシリアライズと混同しないようにしてください + * @param value デシリアライズするタスク + * @return デシリアライズされたタスク + */ + fun deserialize(value: Map>): T +} \ No newline at end of file diff --git a/owl/common/src/test/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicyTest.kt b/owl/common/src/test/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicyTest.kt new file mode 100644 index 00000000..c1a1dc2e --- /dev/null +++ b/owl/common/src/test/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicyTest.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.common.retry + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.time.Instant + +class ExponentialRetryPolicyTest { + @Test + fun exponential0() { + val nextRetry = ExponentialRetryPolicy().nextRetry(Instant.ofEpochSecond(300), 0) + + assertEquals(Instant.ofEpochSecond(300), nextRetry) + } + + @Test + fun exponential1() { + val nextRetry = ExponentialRetryPolicy().nextRetry(Instant.ofEpochSecond(300), 1) + assertEquals(Instant.ofEpochSecond(330), nextRetry) + } +} \ No newline at end of file diff --git a/owl/consumer/build.gradle.kts b/owl/consumer/build.gradle.kts new file mode 100644 index 00000000..4137b56a --- /dev/null +++ b/owl/consumer/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + kotlin("jvm") + id("com.google.protobuf") version "0.9.4" +} + +group = "dev.usbharu" +version = "0.0.1" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test") + implementation("io.grpc:grpc-kotlin-stub:1.4.1") + implementation("io.grpc:grpc-protobuf:1.61.1") + implementation("com.google.protobuf:protobuf-kotlin:3.25.3") + implementation("io.grpc:grpc-netty:1.61.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") + implementation(project(":common")) + protobuf(files(project(":broker").dependencyProject.projectDir.toString() + "/src/main/proto")) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(17) +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.25.3" + } + plugins { + create("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:1.61.1" + } + create("grpckt") { + artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar" + } + } + generateProtoTasks { + all().forEach { + it.plugins { + create("grpc") + create("grpckt") + } + it.builtins { + create("kotlin") + } + } + } +} \ No newline at end of file diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/Consumer.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/Consumer.kt new file mode 100644 index 00000000..40b44201 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/Consumer.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +import dev.usbharu.owl.* +import dev.usbharu.owl.Uuid.UUID +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.property.PropertySerializerFactory +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import org.slf4j.LoggerFactory +import java.time.Instant +import kotlin.math.max + +/** + * Consumer + * + * @property subscribeTaskStub + * @property assignmentTaskStub + * @property taskResultStub + * @property runnerMap + * @property propertySerializerFactory + * @constructor + * TODO + * + * @param consumerConfig + */ +class Consumer( + private val subscribeTaskStub: SubscribeTaskServiceGrpcKt.SubscribeTaskServiceCoroutineStub, + private val assignmentTaskStub: AssignmentTaskServiceGrpcKt.AssignmentTaskServiceCoroutineStub, + private val taskResultStub: TaskResultServiceGrpcKt.TaskResultServiceCoroutineStub, + private val runnerMap: Map, + private val propertySerializerFactory: PropertySerializerFactory, + consumerConfig: ConsumerConfig +) { + + private lateinit var consumerId: UUID + + private lateinit var coroutineScope: CoroutineScope + + private val concurrent = MutableStateFlow(consumerConfig.concurrent) + private val processing = MutableStateFlow(0) + + /** + * Consumerを初期化します + * + * @param name Consumer名 + * @param hostname Consumerのホスト名 + */ + suspend fun init(name: String, hostname: String) { + logger.info("Initialize Consumer name: {} hostname: {}", name, hostname) + logger.debug("Registered Tasks: {}", runnerMap.keys) + consumerId = subscribeTaskStub.subscribeTask(subscribeTaskRequest { + this.name = name + this.hostname = hostname + this.tasks.addAll(runnerMap.keys) + }).id + logger.info("Success initialize consumer. ConsumerID: {}", consumerId) + } + + /** + * タスクの受付を開始します + * + */ + suspend fun start() { + coroutineScope = CoroutineScope(Dispatchers.Default) + coroutineScope { + taskResultStub + .tasKResult(flow { + assignmentTaskStub + .ready(flow { + while (coroutineScope.isActive) { + val andSet = concurrent.getAndUpdate { 0 } + + + if (andSet != 0) { + logger.debug("Request {} tasks.", andSet) + emit(readyRequest { + this.consumerId = consumerId + this.numberOfConcurrent = andSet + }) + continue + } + delay(100) + + concurrent.update { + ((64 - it) - processing.value).coerceIn(0, 64 - max(0, processing.value)) + } + } + }).onEach { + logger.info("Start Task name: {}", it.name) + processing.update { it + 1 } + + try { + + val taskResult = runnerMap.getValue(it.name).run( + TaskRequest( + it.name, + java.util.UUID(it.id.mostSignificantUuidBits, it.id.leastSignificantUuidBits), + it.attempt, + Instant.ofEpochSecond(it.queuedAt.seconds, it.queuedAt.nanos.toLong()), + PropertySerializeUtils.deserialize(propertySerializerFactory, it.propertiesMap) + ) + ) + + emit(taskResult { + this.success = taskResult.success + this.attempt = it.attempt + this.id = it.id + this.result.putAll( + PropertySerializeUtils.serialize( + propertySerializerFactory, taskResult.result + ) + ) + this.message = taskResult.message + }) + logger.info("Success execute task. name: {} success: {}", it.name, taskResult.success) + logger.debug("TRACE RESULT {}", taskResult) + } catch (e: CancellationException) { + logger.warn("Cancelled execute task.", e) + emit(taskResult { + this.success = false + this.attempt = it.attempt + this.id = it.id + this.message = e.localizedMessage + }) + throw e + } catch (e: Exception) { + logger.warn("Failed execute task.", e) + emit(taskResult { + this.success = false + this.attempt = it.attempt + this.id = it.id + this.message = e.localizedMessage + }) + } finally { + processing.update { it - 1 } + concurrent.update { + if (it < 64) { + it + 1 + } else { + 64 + } + } + } + }.flowOn(Dispatchers.Default).collect() + }) + } + } + + /** + * タスクの受付を停止します + * + */ + fun stop() { + logger.info("Stop Consumer. consumerID: {}", consumerId) + coroutineScope.cancel() + } + + companion object { + private val logger = LoggerFactory.getLogger(Consumer::class.java) + } +} \ No newline at end of file diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/ConsumerConfig.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/ConsumerConfig.kt new file mode 100644 index 00000000..a4609e51 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/ConsumerConfig.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +/** + * Consumerの構成 + * + * @property concurrent Consumerのワーカーの同時実行数 + */ +data class ConsumerConfig( + val concurrent: Int +) diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/Main.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/Main.kt new file mode 100644 index 00000000..31fba291 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/Main.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +import kotlinx.coroutines.runBlocking + +fun main() { + val standaloneConsumer = StandaloneConsumer() + + runBlocking { + standaloneConsumer.init() + standaloneConsumer.start() + } + +} \ No newline at end of file diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumer.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumer.kt new file mode 100644 index 00000000..7d1cf295 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumer.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +import dev.usbharu.owl.AssignmentTaskServiceGrpcKt +import dev.usbharu.owl.SubscribeTaskServiceGrpcKt +import dev.usbharu.owl.TaskResultServiceGrpcKt +import dev.usbharu.owl.common.property.CustomPropertySerializerFactory +import dev.usbharu.owl.common.property.PropertySerializerFactory +import io.grpc.ManagedChannelBuilder +import java.nio.file.Path +import java.util.* + +/** + * 単独で起動できるConsumer + * + * @property config Consumerの起動構成 + * @property propertySerializerFactory [dev.usbharu.owl.common.property.PropertyValue]のシリアライザーのファクトリ + */ +class StandaloneConsumer( + private val config: StandaloneConsumerConfig, + private val propertySerializerFactory: PropertySerializerFactory +) { + constructor( + path: Path, + propertySerializerFactory: PropertySerializerFactory = CustomPropertySerializerFactory( + emptySet() + ) + ) : this(StandaloneConsumerConfigLoader.load(path), propertySerializerFactory) + + constructor(string: String) : this(Path.of(string)) + + constructor() : this(Path.of("consumer.properties")) + + private val channel = ManagedChannelBuilder.forAddress(config.address, config.port) + .usePlaintext() + .build() + + private val subscribeStub = SubscribeTaskServiceGrpcKt.SubscribeTaskServiceCoroutineStub(channel) + private val assignmentTaskStub = AssignmentTaskServiceGrpcKt.AssignmentTaskServiceCoroutineStub(channel) + private val taskResultStub = TaskResultServiceGrpcKt.TaskResultServiceCoroutineStub(channel) + + private val taskRunnerMap = ServiceLoader + .load(TaskRunner::class.java) + .associateBy { it.name } + + private val consumer = Consumer( + subscribeTaskStub = subscribeStub, + assignmentTaskStub = assignmentTaskStub, + taskResultStub = taskResultStub, + runnerMap = taskRunnerMap, + propertySerializerFactory = propertySerializerFactory, + consumerConfig = ConsumerConfig(config.concurrency) + ) + + /** + * Consumerを初期化します + * + */ + suspend fun init() { + consumer.init(config.name, config.hostname) + } + + /** + * Consumerのワーカーを起動し、タスクの受付を開始します。 + * + * シャットダウンフックに[stop]が登録されます。 + */ + suspend fun start() { + consumer.start() + Runtime.getRuntime().addShutdownHook(Thread { + consumer.stop() + }) + } + + /** + * Consumerを停止します + * + */ + fun stop() { + consumer.stop() + } + +} \ No newline at end of file diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfig.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfig.kt new file mode 100644 index 00000000..ff31dde8 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfig.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +/** + * 単独で起動できるConsumerの構成 + * + * @property address brokerのアドレス + * @property port brokerのポート + * @property name Consumerの名前 + * @property hostname Consumerのホスト名 + * @property concurrency ConsumerのWorkerの最大同時実行数 + */ +data class StandaloneConsumerConfig( + val address: String, + val port: Int, + val name: String, + val hostname: String, + val concurrency: Int, +) diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfigLoader.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfigLoader.kt new file mode 100644 index 00000000..d11bda43 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfigLoader.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +import java.nio.file.Files +import java.nio.file.Path +import java.util.* + +/** + * 単独で起動できるConsumerの構成のローダー + */ +object StandaloneConsumerConfigLoader { + /** + * [Path]から構成を読み込みます + * + * @param path 読み込むパス + * @return 読み込まれた構成 + */ + fun load(path: Path): StandaloneConsumerConfig { + val properties = Properties() + + properties.load(Files.newInputStream(path)) + + val address = properties.getProperty("address") + val port = properties.getProperty("port").toInt() + val name = properties.getProperty("name") + val hostname = properties.getProperty("hostname") + val concurrency = properties.getProperty("concurrency").toInt() + + return StandaloneConsumerConfig(address, port, name, hostname, concurrency) + } +} \ No newline at end of file diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRequest.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRequest.kt new file mode 100644 index 00000000..006856f2 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRequest.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +import dev.usbharu.owl.common.property.PropertyValue +import java.time.Instant +import java.util.* + +/** + * タスクをConsumerに要求します + * + * @property name タスク名 + * @property id タスクID + * @property attempt 試行回数 + * @property queuedAt タスクがキューに入れられた時間 + * @property properties タスクに渡されたパラメータ + */ +data class TaskRequest( + val name:String, + val id:UUID, + val attempt:Int, + val queuedAt: Instant, + val properties:Map> +) diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskResult.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskResult.kt new file mode 100644 index 00000000..3e21dfb9 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskResult.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +import dev.usbharu.owl.common.property.PropertyValue + +/** + * タスクの実行結果 + * + * @property success 成功したらtrue + * @property result タスクの実行結果のMap + * @property message その他メッセージ + */ +data class TaskResult( + val success: Boolean, + val result: Map>, + val message: String +) diff --git a/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunner.kt b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunner.kt new file mode 100644 index 00000000..613b0166 --- /dev/null +++ b/owl/consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunner.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.consumer + +/** + * タスクを実行するランナー + * + */ +interface TaskRunner { + /** + * 実行するタスク名 + */ + val name: String + + /** + * タスクを実行する + * + * @param taskRequest 実行するタスク + * @return タスク実行結果 + */ + suspend fun run(taskRequest: TaskRequest): TaskResult +} \ No newline at end of file diff --git a/owl/gradle.properties b/owl/gradle.properties new file mode 100644 index 00000000..c02d6c9d --- /dev/null +++ b/owl/gradle.properties @@ -0,0 +1,5 @@ +kotlin.code.style=official +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureondemand=true + diff --git a/owl/gradle/wrapper/gradle-wrapper.jar b/owl/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/owl/gradle/wrapper/gradle-wrapper.jar differ diff --git a/owl/gradle/wrapper/gradle-wrapper.properties b/owl/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..c3806883 --- /dev/null +++ b/owl/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Feb 18 23:33:24 JST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/owl/gradlew b/owl/gradlew new file mode 100644 index 00000000..1b6c7873 --- /dev/null +++ b/owl/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/owl/gradlew.bat b/owl/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/owl/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/owl/producer/api/build.gradle.kts b/owl/producer/api/build.gradle.kts new file mode 100644 index 00000000..7e049bf8 --- /dev/null +++ b/owl/producer/api/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + kotlin("jvm") +} + +group = "dev.usbharu" +version = "0.0.1" + +repositories { + mavenCentral() +} + +dependencies { + api(project(":common")) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(17) +} \ No newline at end of file diff --git a/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducer.kt b/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducer.kt new file mode 100644 index 00000000..21495894 --- /dev/null +++ b/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducer.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.producer.api + +import dev.usbharu.owl.common.task.PublishedTask +import dev.usbharu.owl.common.task.Task +import dev.usbharu.owl.common.task.TaskDefinition + +/** + * タスクを発生させるクライアント + * + */ +interface OwlProducer { + + /** + * Producerを開始します + * + */ + suspend fun start() + + /** + * タスク定義を登録します + * + * @param T 登録するタスク + * @param taskDefinition 登録するタスクの定義 + */ + suspend fun registerTask(taskDefinition: TaskDefinition) + + /** + * タスクを公開します。タスクは定義済みである必要があります。 + * + * @param T 公開するタスク + * @param task タスクの詳細 + * @return 公開されたタスク + */ + suspend fun publishTask(task: T): PublishedTask +} diff --git a/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilder.kt b/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilder.kt new file mode 100644 index 00000000..4d1c21ab --- /dev/null +++ b/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilder.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.producer.api + +interface OwlProducerBuilder

{ + fun config(): T + fun apply(owlProducerConfig: T) + + fun build(): P +} \ No newline at end of file diff --git a/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilderConfig.kt b/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilderConfig.kt new file mode 100644 index 00000000..2b6548f0 --- /dev/null +++ b/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilderConfig.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.producer.api + +fun

, C : OwlProducerConfig> OWL( + owlProducerBuilder: T, + configBlock: C.() -> Unit +) { + owlProducerBuilder.apply(owlProducerBuilder.config().apply { configBlock() }) +} \ No newline at end of file diff --git a/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerConfig.kt b/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerConfig.kt new file mode 100644 index 00000000..557547ce --- /dev/null +++ b/owl/producer/api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerConfig.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.producer.api + +interface OwlProducerConfig { +} \ No newline at end of file diff --git a/owl/producer/default/build.gradle.kts b/owl/producer/default/build.gradle.kts new file mode 100644 index 00000000..722c7de8 --- /dev/null +++ b/owl/producer/default/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + kotlin("jvm") + id("com.google.protobuf") version "0.9.4" +} + +group = "dev.usbharu" +version = "0.0.1" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test") + implementation(project(":producer:api")) + implementation("io.grpc:grpc-kotlin-stub:1.4.1") + implementation("io.grpc:grpc-protobuf:1.61.1") + implementation("com.google.protobuf:protobuf-kotlin:3.25.3") + implementation("io.grpc:grpc-netty:1.61.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") + implementation(project(":common")) + protobuf(files(project(":broker").dependencyProject.projectDir.toString() + "/src/main/proto")) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(17) +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.25.3" + } + plugins { + create("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:1.61.1" + } + create("grpckt") { + artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar" + } + } + generateProtoTasks { + all().forEach { + it.plugins { + create("grpc") + create("grpckt") + } + it.builtins { + create("kotlin") + } + } + } +} \ No newline at end of file diff --git a/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducer.kt b/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducer.kt new file mode 100644 index 00000000..33a14e34 --- /dev/null +++ b/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducer.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.producer.defaultimpl + +import com.google.protobuf.timestamp +import dev.usbharu.owl.* +import dev.usbharu.owl.Uuid.UUID +import dev.usbharu.owl.common.property.PropertySerializeUtils +import dev.usbharu.owl.common.task.PublishedTask +import dev.usbharu.owl.common.task.Task +import dev.usbharu.owl.common.task.TaskDefinition +import dev.usbharu.owl.producer.api.OwlProducer +import java.time.Instant + +class DefaultOwlProducer(private val defaultOwlProducerConfig: DefaultOwlProducerConfig) : OwlProducer { + + lateinit var producerId: UUID + lateinit var producerServiceCoroutineStub: ProducerServiceGrpcKt.ProducerServiceCoroutineStub + lateinit var defineTaskServiceCoroutineStub: DefinitionTaskServiceGrpcKt.DefinitionTaskServiceCoroutineStub + lateinit var taskPublishServiceCoroutineStub: TaskPublishServiceGrpcKt.TaskPublishServiceCoroutineStub + val map = mutableMapOf, TaskDefinition<*>>() + override suspend fun start() { + producerServiceCoroutineStub = + ProducerServiceGrpcKt.ProducerServiceCoroutineStub(defaultOwlProducerConfig.channel) + producerId = producerServiceCoroutineStub.registerProducer(producer { + this.name = defaultOwlProducerConfig.name + this.hostname = defaultOwlProducerConfig.hostname + }).id + + defineTaskServiceCoroutineStub = + DefinitionTaskServiceGrpcKt.DefinitionTaskServiceCoroutineStub(defaultOwlProducerConfig.channel) + + taskPublishServiceCoroutineStub = + TaskPublishServiceGrpcKt.TaskPublishServiceCoroutineStub(defaultOwlProducerConfig.channel) + } + + + override suspend fun registerTask(taskDefinition: TaskDefinition) { + defineTaskServiceCoroutineStub.register(taskDefinition { + this.producerId = this@DefaultOwlProducer.producerId + this.name = taskDefinition.name + this.maxRetry = taskDefinition.maxRetry + this.priority = taskDefinition.priority + this.retryPolicy = taskDefinition.retryPolicy + this.timeoutMilli = taskDefinition.timeoutMilli + this.propertyDefinitionHash = taskDefinition.propertyDefinition.hash() + }) + } + + override suspend fun publishTask(task: T): PublishedTask { + val taskDefinition = map.getValue(task::class.java) as TaskDefinition + val properties = PropertySerializeUtils.serialize( + defaultOwlProducerConfig.propertySerializerFactory, + taskDefinition.serialize(task) + ) + val now = Instant.now() + val publishTask = taskPublishServiceCoroutineStub.publishTask( + dev.usbharu.owl.publishTask { + this.producerId = this@DefaultOwlProducer.producerId + + this.publishedAt = timestamp { + this.seconds = now.epochSecond + this.nanos = now.nano + } + this.name = taskDefinition.name + this.properties.putAll(properties) + } + ) + + return PublishedTask( + task, + java.util.UUID(publishTask.id.mostSignificantUuidBits, publishTask.id.leastSignificantUuidBits), + now + ) + } +} \ No newline at end of file diff --git a/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerBuilder.kt b/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerBuilder.kt new file mode 100644 index 00000000..8100088f --- /dev/null +++ b/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerBuilder.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.dev.usbharu.owl.producer.defaultimpl + +import dev.usbharu.owl.producer.api.OwlProducerBuilder +import dev.usbharu.owl.producer.defaultimpl.DefaultOwlProducer +import dev.usbharu.owl.producer.defaultimpl.DefaultOwlProducerConfig +import io.grpc.ManagedChannelBuilder + +class DefaultOwlProducerBuilder : OwlProducerBuilder { + + var config: DefaultOwlProducerConfig = config() + + override fun config(): DefaultOwlProducerConfig { + val defaultOwlProducerConfig = DefaultOwlProducerConfig() + + with(defaultOwlProducerConfig) { + channel = ManagedChannelBuilder.forAddress("localhost", 50051).usePlaintext().build() + } + + return defaultOwlProducerConfig + } + + override fun build(): DefaultOwlProducer { + return DefaultOwlProducer( + config + ) + } + + override fun apply(owlProducerConfig: DefaultOwlProducerConfig) { + this.config = owlProducerConfig + } +} + +val DEFAULT by lazy { DefaultOwlProducerBuilder() } \ No newline at end of file diff --git a/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerConfig.kt b/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerConfig.kt new file mode 100644 index 00000000..a5eaddf6 --- /dev/null +++ b/owl/producer/default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerConfig.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 usbharu + * + * 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 + * + * 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 dev.usbharu.owl.producer.defaultimpl + +import dev.usbharu.owl.common.property.PropertySerializerFactory +import dev.usbharu.owl.producer.api.OwlProducerConfig +import io.grpc.Channel + +class DefaultOwlProducerConfig : OwlProducerConfig { + lateinit var channel: Channel + lateinit var name: String + lateinit var hostname: String + lateinit var propertySerializerFactory: PropertySerializerFactory +} \ No newline at end of file diff --git a/owl/settings.gradle.kts b/owl/settings.gradle.kts new file mode 100644 index 00000000..87d7071c --- /dev/null +++ b/owl/settings.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "owl" +include("common") +include("producer:api") +findProject(":producer:api")?.name = "api" +include("broker") +include("broker:broker-mongodb") +findProject(":broker:broker-mongodb")?.name = "broker-mongodb" +include("producer:default") +findProject(":producer:default")?.name = "default" +include("consumer")