From 5420e08cc1629e7f5fae52fa2b205030f734872f Mon Sep 17 00:00:00 2001 From: Brandon Lamb Date: Sat, 29 Dec 2018 15:50:44 -0800 Subject: [PATCH] Initial commit for a Kotiln, CDI, JAX-RS example - Kotlin - CDI - JAX-RS - Domain-Driven Design - Ports and Adapters - Constructor injection, interfaces --- examples/kotlin-cdi-jaxrs/.gitignore | 43 ++++ examples/kotlin-cdi-jaxrs/Dockerfile | 17 ++ examples/kotlin-cdi-jaxrs/build.sh | 10 + examples/kotlin-cdi-jaxrs/pom.xml | 203 ++++++++++++++++++ .../application/CreatePlayerHandler.kt | 37 ++++ .../domain/player/BlacklistNameValidator.kt | 18 ++ .../example/domain/player/PlayerService.kt | 32 +++ .../domain/player/api/PlayerReadRepository.kt | 19 ++ .../domain/player/api/PlayerService.kt | 22 ++ .../domain/player/api/PlayerValidator.kt | 13 ++ .../player/api/PlayerWriteRepository.kt | 13 ++ .../com/example/domain/player/exceptions.kt | 3 + .../com/example/domain/player/models.kt | 15 ++ .../infrastructure/basic/PlayerDatabase.kt | 20 ++ .../basic/PlayerReadRepository.kt | 13 ++ .../basic/PlayerWriteRepository.kt | 11 + .../infrastructure/config/ConfigProducer.kt | 15 ++ .../example/infrastructure/config/models.kt | 3 + .../kotlin/com/example/rest/Application.kt | 6 + .../com/example/rest/PlayerController.kt | 56 +++++ .../rest/exception/BaseExceptionMapper.kt | 19 ++ .../exception/ClientErrorExceptionMapper.kt | 20 ++ .../ConstraintViolationExceptionMapper.kt | 33 +++ .../IllegalArgumentExceptionMapper.kt | 19 ++ .../exception/JsonParsingExceptionMapper.kt | 20 ++ .../exception/NullPointerExceptionMapper.kt | 19 ++ .../exception/NumberFormatExceptionMapper.kt | 19 ++ .../WebApplicationExceptionMapper.kt | 20 ++ .../kotlin/com/example/rest/exceptions.kt | 4 + .../main/kotlin/com/example/rest/models.kt | 16 ++ .../src/main/tomee/bin/setenv.sh | 2 + .../src/main/webapp/WEB-INF/beans.xml | 6 + .../src/main/webapp/WEB-INF/ejb-jar.xml | 9 + .../src/main/webapp/WEB-INF/openejb-jar.xml | 16 ++ .../src/main/webapp/WEB-INF/resources.xml | 14 ++ .../src/main/webapp/WEB-INF/web.xml | 5 + .../kotlin-cdi-jaxrs/src/test/kotlin/.gitkeep | 0 .../src/test/resources/arquillian.xml | 31 +++ .../src/test/resources/beans.xml | 6 + 39 files changed, 847 insertions(+) create mode 100644 examples/kotlin-cdi-jaxrs/.gitignore create mode 100644 examples/kotlin-cdi-jaxrs/Dockerfile create mode 100755 examples/kotlin-cdi-jaxrs/build.sh create mode 100644 examples/kotlin-cdi-jaxrs/pom.xml create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/application/CreatePlayerHandler.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/BlacklistNameValidator.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/PlayerService.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerReadRepository.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerService.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerValidator.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerWriteRepository.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/exceptions.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/models.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerDatabase.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerReadRepository.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerWriteRepository.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/config/ConfigProducer.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/config/models.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/Application.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/PlayerController.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/BaseExceptionMapper.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/ClientErrorExceptionMapper.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/ConstraintViolationExceptionMapper.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/IllegalArgumentExceptionMapper.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/JsonParsingExceptionMapper.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/NullPointerExceptionMapper.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/NumberFormatExceptionMapper.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/WebApplicationExceptionMapper.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exceptions.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/models.kt create mode 100644 examples/kotlin-cdi-jaxrs/src/main/tomee/bin/setenv.sh create mode 100644 examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/beans.xml create mode 100644 examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/ejb-jar.xml create mode 100644 examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/openejb-jar.xml create mode 100644 examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/resources.xml create mode 100644 examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/web.xml create mode 100644 examples/kotlin-cdi-jaxrs/src/test/kotlin/.gitkeep create mode 100644 examples/kotlin-cdi-jaxrs/src/test/resources/arquillian.xml create mode 100644 examples/kotlin-cdi-jaxrs/src/test/resources/beans.xml diff --git a/examples/kotlin-cdi-jaxrs/.gitignore b/examples/kotlin-cdi-jaxrs/.gitignore new file mode 100644 index 00000000000..34aeb6077ae --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/.gitignore @@ -0,0 +1,43 @@ +# Java # +*.class + +# Package Files # +*.jar +*.war +*.ear + +# IDEA # +*.iml +.idea + +# eclipse specific git ignore +.project +.metadata +.classpath +.settings/ + +# Maven files # +target +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml + +# Misc # +*.log +*.odt# +*~ +.DS_Store +public/ + +#Git build properties omission +src/main/resources/git.properties +src/main/resources/mergedconf +test.sh +compile.sh + +#JMeter +*.jtl +new/ diff --git a/examples/kotlin-cdi-jaxrs/Dockerfile b/examples/kotlin-cdi-jaxrs/Dockerfile new file mode 100644 index 00000000000..cffb9bdf8ac --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/Dockerfile @@ -0,0 +1,17 @@ +#FROM adoptopenjdk/openjdk8-openj9:jdk8u192-b12_openj9-0.11.0-alpine-slim as builder +FROM maven:3.6.0-jdk-8-slim as builder + +WORKDIR /usr/src/app + +COPY pom.xml . +RUN mvn clean package + +COPY . . +RUN mvn package + +FROM tomee:8-jre-8.0.0-M1-plus + +RUN rm -rf /usr/local/tomee/webapps/* + +COPY --from=builder /usr/src/app/target/ROOT.war /usr/local/tomee/webapps/ +#COPY target/ROOT.war /usr/local/tomee/webapps/ diff --git a/examples/kotlin-cdi-jaxrs/build.sh b/examples/kotlin-cdi-jaxrs/build.sh new file mode 100755 index 00000000000..8bc778a4459 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/build.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +docker build --tag kotlin-cdi-jaxrs:latest . + +docker run -it --rm \ + --memory 128M \ + --publish 8080:8080 \ + --name kotlin-cdi-jaxrs \ + kotlin-cdi-jaxrs:latest + diff --git a/examples/kotlin-cdi-jaxrs/pom.xml b/examples/kotlin-cdi-jaxrs/pom.xml new file mode 100644 index 00000000000..3efe2284593 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/pom.xml @@ -0,0 +1,203 @@ + + + 4.0.0 + com.example + 1.0-SNAPSHOT + kotlin-cdi-jaxrs + kotlin-cdi-jaxrs + war + + + 1.8 + UTF-8 + UTF-8 + 1.8 + 1.8 + 1.8 + ${maven.compiler.target} + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.source} + ${maven.compiler.testTarget} + ${maven.compiler.testSource} + true + ${maven.compiler.argument.target} + source-release + + 1.3.11 + 8.0.0-M1 + 3.1.0 + + true + + + + + + org.apache.tomee + tomee + 8.0.0-M1 + pom + import + + + + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + + + + org.jetbrains.kotlin + kotlin-annotation-processing-maven + ${kotlin.version} + provided + + + + org.apache.tomee + javaee-api + 8.0 + provided + + + + org.apache.geronimo.specs + geronimo-jsonb_1.0_spec + 1.0 + provided + + + + org.eclipse.microprofile.config + microprofile-config-api + 1.3 + + + + + ROOT + src/main/kotlin + src/test/kotlin + + + + src/main/resources + + + + ${project.build.directory}/resources + + + + + + src/test/resources + true + + + + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + + no-arg + all-open + + + + + + + + + + + + + + + + + + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + compile + + compile + + + + src/main/kotlin + + + + + + test-compile + + test-compile + + + + src/test/kotlin + target/generated-sources/kapt/test + + + + + + + org.apache.tomee.maven + tomee-maven-plugin + ${version.tomee} + + true + org.apache.tomee + apache-tomee + plus + ${version.tomee} + 8080 + false + + + + org.apache.maven.plugins + maven-war-plugin + ${version.war.plugin} + + false + + + + + diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/application/CreatePlayerHandler.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/application/CreatePlayerHandler.kt new file mode 100644 index 00000000000..c6773667b2e --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/application/CreatePlayerHandler.kt @@ -0,0 +1,37 @@ +package com.example.application + +import com.example.domain.player.CreatePlayer +import com.example.domain.player.PlayerCreated +import com.example.domain.player.api.PlayerService +import java.util.logging.Logger.getLogger +import javax.enterprise.event.Observes +import javax.inject.Inject +import javax.inject.Singleton + +/** + * CDI Event Handler for NewPlayer + * + * Constructor injected dependencies include the player service + */ +@Singleton +class CreatePlayerHandler @Inject constructor( + private val playerService: PlayerService +) { + private val logger = getLogger(CreatePlayerHandler::class.java.simpleName) + + /** + * Observe the NewPlayer CDI event (domain command) and call the player service to create the player + */ + fun handle(@Observes cmd: CreatePlayer) { + playerService.create(cmd.player.copy(id = playerService.generateId())) + } + + /** + * Observe the PlayerCreated CDI event and react by sending an email or slack, then logging the event + */ + fun handle(@Observes event: PlayerCreated) { + // Send an email + // Send a slack notification + logger.info("PlayerCreated=${event.player.id}") + } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/BlacklistNameValidator.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/BlacklistNameValidator.kt new file mode 100644 index 00000000000..4e7ef8011ee --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/BlacklistNameValidator.kt @@ -0,0 +1,18 @@ +package com.example.domain.player + +import com.example.domain.player.api.PlayerValidator +import com.example.infrastructure.config.AppConfig +import javax.inject.Inject +import javax.inject.Singleton + +/** + * A player validator class, which will throw an exception if the player name is an admin name + */ +@Singleton +class BlacklistNameValidator @Inject constructor(private val appConfig: AppConfig) : PlayerValidator { + override fun validate(player: Player) { + if (appConfig.admins.contains(player.name.trim().toLowerCase())) { + throw BlacklistedNameException(player.name) + } + } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/PlayerService.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/PlayerService.kt new file mode 100644 index 00000000000..7566f13ef20 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/PlayerService.kt @@ -0,0 +1,32 @@ +package com.example.domain.player + +import com.example.domain.player.api.PlayerService +import com.example.domain.player.api.PlayerValidator +import com.example.domain.player.api.PlayerWriteRepository +import javax.enterprise.event.Event +import javax.enterprise.inject.Instance +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Player Domain Service Implementation + * + * Handles validation checking, persisting the new player, and firing domain events + */ +@Singleton +class PlayerService @Inject constructor( + private val validators: Instance, + private val playerWriteRepository: PlayerWriteRepository, + private val playerCreated: Event +): PlayerService { + override fun create(player: Player) { + // Iterate over each PlayerValidate, which will throw a PlayerValidationException on any errors + validators.forEach { it.validate(player) } + + // Persist the new player + playerWriteRepository.create(player) + + // Fire a domain event for PlayerCreated + playerCreated.fire(PlayerCreated(player)) + } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerReadRepository.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerReadRepository.kt new file mode 100644 index 00000000000..83bd08caf3e --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerReadRepository.kt @@ -0,0 +1,19 @@ +package com.example.domain.player.api + +import com.example.domain.player.Player +import com.example.domain.player.PlayerId + +/** + * NewPlayer Read (CQRS) Infrastructure Service + */ +interface PlayerReadRepository { + /** + * Find a player by ID + */ + fun findById(id: PlayerId): Player? + + /** + * Find a player by name + */ + fun findByName(name: String): Player? +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerService.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerService.kt new file mode 100644 index 00000000000..62bb3f3807d --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerService.kt @@ -0,0 +1,22 @@ +package com.example.domain.player.api + +import com.example.domain.player.Player +import com.example.domain.player.PlayerId +import java.util.UUID + +/** + * NewPlayer Domain Service + */ +interface PlayerService { + /** + * Generate a new player ID. + * + * Default implementation + */ + fun generateId(): PlayerId = UUID.randomUUID().toString() + + /** + * Create a player + */ + fun create(player: Player) +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerValidator.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerValidator.kt new file mode 100644 index 00000000000..879bb7faa0a --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerValidator.kt @@ -0,0 +1,13 @@ +package com.example.domain.player.api + +import com.example.domain.player.Player + +/** + * Player Validator Domain Service + */ +interface PlayerValidator { + /** + * Validate player domain model + */ + fun validate(player: Player) +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerWriteRepository.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerWriteRepository.kt new file mode 100644 index 00000000000..20a085bc87c --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/api/PlayerWriteRepository.kt @@ -0,0 +1,13 @@ +package com.example.domain.player.api + +import com.example.domain.player.Player + +/** + * NewPlayer Write (CQRS) Infrastructure Service + */ +interface PlayerWriteRepository { + /** + * Create a player, persisting to database, file, etc + */ + fun create(player: Player) +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/exceptions.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/exceptions.kt new file mode 100644 index 00000000000..d54126cdf94 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/exceptions.kt @@ -0,0 +1,3 @@ +package com.example.domain.player + +class BlacklistedNameException(val name: String) : Exception() diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/models.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/models.kt new file mode 100644 index 00000000000..23309b88839 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/domain/player/models.kt @@ -0,0 +1,15 @@ +package com.example.domain.player + +// Domain Type Aliases +typealias PlayerId = String + +// Domain Models +data class Player(val id: PlayerId?, val name: String) + +// Domain Commands +sealed class Command +data class CreatePlayer(val player: Player) : Command() + +// Domain Events +sealed class Event +data class PlayerCreated(val player: Player) : Event() diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerDatabase.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerDatabase.kt new file mode 100644 index 00000000000..b65f700fe26 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerDatabase.kt @@ -0,0 +1,20 @@ +package com.example.infrastructure.basic + +import com.example.domain.player.Player +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Singleton + +@Singleton +class PlayerDatabase { + private val players = ConcurrentHashMap(mapOf( + "admin" to Player("admin", "Admin User 1"), + "bob" to Player("bob", "Admin User 2") + )) + + fun persist(player: Player) { + players.putIfAbsent(player.id!!, player) + } + + fun findById(id: String): Player? = players[id] + fun findByName(name: String): Player? = players.entries.firstOrNull { it.value.name == name.trim() }?.value +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerReadRepository.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerReadRepository.kt new file mode 100644 index 00000000000..827d868793c --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerReadRepository.kt @@ -0,0 +1,13 @@ +package com.example.infrastructure.basic + +import com.example.domain.player.Player +import com.example.domain.player.PlayerId +import com.example.domain.player.api.PlayerReadRepository +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PlayerReadRepository @Inject constructor(private val playerDatabase: PlayerDatabase): PlayerReadRepository { + override fun findById(id: PlayerId): Player? = playerDatabase.findById(id) + override fun findByName(name: String): Player? = playerDatabase.findByName(name) +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerWriteRepository.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerWriteRepository.kt new file mode 100644 index 00000000000..dc9913a0d6c --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/basic/PlayerWriteRepository.kt @@ -0,0 +1,11 @@ +package com.example.infrastructure.basic + +import com.example.domain.player.Player +import com.example.domain.player.api.PlayerWriteRepository +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PlayerWriteRepository @Inject constructor(private val playerDatabase: PlayerDatabase): PlayerWriteRepository { + override fun create(player: Player) = playerDatabase.persist(player) +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/config/ConfigProducer.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/config/ConfigProducer.kt new file mode 100644 index 00000000000..875a50c126d --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/config/ConfigProducer.kt @@ -0,0 +1,15 @@ +package com.example.infrastructure.config + +import javax.enterprise.inject.Produces +import javax.inject.Singleton + +/** + * Configuration producer creates a config object for CDI, using TypeSafe Config + * + * Config file located (by default) at resources/application.conf + */ +class ConfigProducer { + @Produces + @Singleton + fun create(): AppConfig = AppConfig(listOf("admin", "bob")) +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/config/models.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/config/models.kt new file mode 100644 index 00000000000..ef7ea832d99 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/infrastructure/config/models.kt @@ -0,0 +1,3 @@ +package com.example.infrastructure.config + +class AppConfig(val admins: List) diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/Application.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/Application.kt new file mode 100644 index 00000000000..f1a47d09867 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/Application.kt @@ -0,0 +1,6 @@ +package com.example.rest + +import javax.ws.rs.ApplicationPath + +@ApplicationPath("/") +class Application : javax.ws.rs.core.Application() diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/PlayerController.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/PlayerController.kt new file mode 100644 index 00000000000..3cdc8c08ea6 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/PlayerController.kt @@ -0,0 +1,56 @@ +package com.example.rest + +import com.example.domain.player.CreatePlayer +import com.example.domain.player.api.PlayerReadRepository +import java.net.URI +import javax.enterprise.event.Event +import javax.inject.Inject +import javax.inject.Singleton +import javax.ws.rs.Consumes +import javax.ws.rs.DefaultValue +import javax.ws.rs.GET +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType.APPLICATION_JSON +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.created +import javax.ws.rs.core.Response.ok + +@Singleton +@Path("/") +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +class PlayerController @Inject constructor( + private val createPlayer: Event, + private val playerReadRepository: PlayerReadRepository +) { + @GET + @Path("/players/{id}") + fun getPlayer( + @PathParam("id") + @DefaultValue("") + id: String + ): Response { + // Looking up a user via a read repository does not need to go through a domain service + val player = playerReadRepository.findById(id) ?: throw PlayerNotFoundException(id) + return ok(player.toRest()).build() + } + + @POST + @Path("/players") + fun create(player: NewPlayer): Response { + // Synchronously fire a CreatePlayer CDI Event (command). This is one technique to de-couple + // your code by not needing to know how/what to call to handle creation of a player (other than emitting a command) + createPlayer.fire(CreatePlayer(player.toDomain())) + + // Because the CDI Event was fired synchronously, we can read back the newly created player. + // If for some reason we cannot find the newly created user, throw an explicit exception, + // which will be handled by an associated ExceptionMapper (see exceptions package) + val newPlayer = playerReadRepository.findByName(player.name) ?: throw CreatePlayerException(player) + + // Finally, return a 201 Created with a Location header to the newly created Player resource + return created(URI.create("/players/${newPlayer.id}")).build() + } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/BaseExceptionMapper.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/BaseExceptionMapper.kt new file mode 100644 index 00000000000..372b3960929 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/BaseExceptionMapper.kt @@ -0,0 +1,19 @@ +package com.example.rest.exception + +import com.example.rest.ApiError +import java.util.logging.Logger.getLogger +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR +import javax.ws.rs.core.Response.status +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +@Provider +class BaseExceptionMapper : ExceptionMapper { + private val logger = getLogger(BaseExceptionMapper::class.java.simpleName) + + override fun toResponse(e: Exception): Response = status(INTERNAL_SERVER_ERROR) + .entity(ApiError(100, "Generic Error")) + .build() + .also { logger.warning(e.message ?: "No exception message") } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/ClientErrorExceptionMapper.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/ClientErrorExceptionMapper.kt new file mode 100644 index 00000000000..301deec0343 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/ClientErrorExceptionMapper.kt @@ -0,0 +1,20 @@ +package com.example.rest.exception + +import com.example.rest.ApiError +import java.util.logging.Logger +import javax.ws.rs.ClientErrorException +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.Status.BAD_REQUEST +import javax.ws.rs.core.Response.status +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +@Provider +class ClientErrorExceptionMapper : ExceptionMapper { + private val logger = Logger.getLogger(ClientErrorExceptionMapper::class.java.simpleName) + + override fun toResponse(e: ClientErrorException): Response = status(BAD_REQUEST) + .entity(ApiError(101, "A client error occurred")) + .build() + .also { logger.warning(e.message ?: "No exception message") } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/ConstraintViolationExceptionMapper.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/ConstraintViolationExceptionMapper.kt new file mode 100644 index 00000000000..e5713b9f047 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/ConstraintViolationExceptionMapper.kt @@ -0,0 +1,33 @@ +package com.example.rest.exception + +import com.example.rest.ApiError +import java.util.logging.Logger +import javax.validation.ConstraintViolationException +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.Status.BAD_REQUEST +import javax.ws.rs.core.Response.status +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +@Provider +class ConstraintViolationExceptionMapper : ExceptionMapper { + private val logger = Logger.getLogger(ConstraintViolationExceptionMapper::class.java.simpleName) + + /** + * This code allows a bean validation message to be a code which will be looked up in the api codes map, + * or it can be a custom developer message. + */ + override fun toResponse(e: ConstraintViolationException): Response { + val message = e.constraintViolations.first().message ?: "100008" + + return try { + val code = message.toInt() + status(BAD_REQUEST) + .entity(ApiError(code, "A validation error occurred")) + .build() + .also { logger.warning(e.message ?: "No exception message") } + } catch (nfe: NumberFormatException) { + status(BAD_REQUEST).entity(ApiError(100, message)).build() + } + } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/IllegalArgumentExceptionMapper.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/IllegalArgumentExceptionMapper.kt new file mode 100644 index 00000000000..3bdfa97e5ff --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/IllegalArgumentExceptionMapper.kt @@ -0,0 +1,19 @@ +package com.example.rest.exception + +import com.example.rest.ApiError +import java.util.logging.Logger +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.Status.BAD_REQUEST +import javax.ws.rs.core.Response.status +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +@Provider +class IllegalArgumentExceptionMapper : ExceptionMapper { + private val logger = Logger.getLogger(IllegalArgumentExceptionMapper::class.java.simpleName) + + override fun toResponse(e: IllegalArgumentException): Response = status(BAD_REQUEST) + .entity(ApiError(106, "An illegal argument error occurred")) + .build() + .also { logger.warning(e.message ?: "No exception message") } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/JsonParsingExceptionMapper.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/JsonParsingExceptionMapper.kt new file mode 100644 index 00000000000..7050f397661 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/JsonParsingExceptionMapper.kt @@ -0,0 +1,20 @@ +package com.example.rest.exception + +import com.example.rest.ApiError +import java.util.logging.Logger +import javax.json.stream.JsonParsingException +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR +import javax.ws.rs.core.Response.status +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +@Provider +class JsonParsingExceptionMapper : ExceptionMapper { + private val logger = Logger.getLogger(JsonParsingExceptionMapper::class.java.simpleName) + + override fun toResponse(e: JsonParsingException): Response = status(INTERNAL_SERVER_ERROR) + .entity(ApiError(110, "A JSON parsing error occurred")) + .build() + .also { logger.warning(e.message ?: "No exception message") } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/NullPointerExceptionMapper.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/NullPointerExceptionMapper.kt new file mode 100644 index 00000000000..feffc3cf8f5 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/NullPointerExceptionMapper.kt @@ -0,0 +1,19 @@ +package com.example.rest.exception + +import com.example.rest.ApiError +import java.util.logging.Logger +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR +import javax.ws.rs.core.Response.status +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +@Provider +class NullPointerExceptionMapper: ExceptionMapper { + private val logger = Logger.getLogger(NullPointerExceptionMapper::class.java.simpleName) + + override fun toResponse(e: NullPointerException): Response = status(INTERNAL_SERVER_ERROR) + .entity(ApiError(105, "A null pointer error occurred")) + .build() + .also { logger.warning(e.message ?: "No exception message") } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/NumberFormatExceptionMapper.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/NumberFormatExceptionMapper.kt new file mode 100644 index 00000000000..51f9390817a --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/NumberFormatExceptionMapper.kt @@ -0,0 +1,19 @@ +package com.example.rest.exception + +import com.example.rest.ApiError +import java.util.logging.Logger +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR +import javax.ws.rs.core.Response.status +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +@Provider +class NumberFormatExceptionMapper : ExceptionMapper { + private val logger = Logger.getLogger(NumberFormatExceptionMapper::class.java.simpleName) + + override fun toResponse(e: NumberFormatException): Response = status(INTERNAL_SERVER_ERROR).entity( + ApiError(111, "A number format error occurred")) + .build() + .also { logger.warning(e.message ?: "No exception message") } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/WebApplicationExceptionMapper.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/WebApplicationExceptionMapper.kt new file mode 100644 index 00000000000..e8be14dd36e --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exception/WebApplicationExceptionMapper.kt @@ -0,0 +1,20 @@ +package com.example.rest.exception + +import com.example.rest.ApiError +import java.util.logging.Logger +import javax.ws.rs.WebApplicationException +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.Status.BAD_REQUEST +import javax.ws.rs.core.Response.status +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +@Provider +class WebApplicationExceptionMapper : ExceptionMapper { + private val logger = Logger.getLogger(WebApplicationExceptionMapper::class.java.simpleName) + + override fun toResponse(e: WebApplicationException): Response = status(BAD_REQUEST) + .entity(ApiError(117, "A web application error occurred")) + .build() + .also { logger.warning(e.message ?: "No exception message") } +} diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exceptions.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exceptions.kt new file mode 100644 index 00000000000..68b1dc06f06 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/exceptions.kt @@ -0,0 +1,4 @@ +package com.example.rest + +class CreatePlayerException(val player: NewPlayer) : Exception() +class PlayerNotFoundException(val id: String) : Exception() diff --git a/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/models.kt b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/models.kt new file mode 100644 index 00000000000..762723233bf --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/kotlin/com/example/rest/models.kt @@ -0,0 +1,16 @@ +package com.example.rest + +import com.example.domain.player.Player as DomainPlayer + +// Data class for sending proper API errors back to consumers +data class ApiError(val code: Int, val message: String) + +// Write-side Model (CQRS) +data class NewPlayer(var name: String = "") + +// Read-side Model (CQRS) +data class Player(val id: String, val name: String) + +// Extension Functions for port <-> domain model conversions +internal fun NewPlayer.toDomain() = DomainPlayer(null, name) +internal fun DomainPlayer.toRest() = Player(id ?: "", name) diff --git a/examples/kotlin-cdi-jaxrs/src/main/tomee/bin/setenv.sh b/examples/kotlin-cdi-jaxrs/src/main/tomee/bin/setenv.sh new file mode 100644 index 00000000000..13f47935d98 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/tomee/bin/setenv.sh @@ -0,0 +1,2 @@ +#!/bin/sh + diff --git a/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/beans.xml b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 00000000000..ca873f517fb --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/beans.xml @@ -0,0 +1,6 @@ + + + diff --git a/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/ejb-jar.xml b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/ejb-jar.xml new file mode 100644 index 00000000000..bc7eb2afe9d --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/ejb-jar.xml @@ -0,0 +1,9 @@ + + + + + javax.validation.ConstraintViolationException + false + + + diff --git a/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/openejb-jar.xml b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/openejb-jar.xml new file mode 100644 index 00000000000..2007f786b3a --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/openejb-jar.xml @@ -0,0 +1,16 @@ + + + + + cxf.jaxrs.providers = johnzonProvider, + com.example.ports.rest.exception.ClientErrorExceptionMapper, + com.example.ports.rest.exception.ConstraintViolationExceptionMapper, + com.example.ports.rest.exception.IllegalArgumentExceptionMapper, + com.example.ports.rest.exception.JsonParsingExceptionMapper, + com.example.ports.rest.exception.NullPointerExceptionMapper, + com.example.ports.rest.exception.NumberFormatExceptionMapper, + com.example.ports.rest.exception.WebApplicationExceptionMapper, + com.example.ports.rest.exception.BaseExceptionMapper + + + diff --git a/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/resources.xml b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/resources.xml new file mode 100644 index 00000000000..217aaeaf29d --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/resources.xml @@ -0,0 +1,14 @@ + + + + # any config + #bufferSize = 1048576 + skipNull = false + skipEmptyArray = false + pretty = true + + diff --git a/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/web.xml b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..230ea28b658 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,5 @@ + + diff --git a/examples/kotlin-cdi-jaxrs/src/test/kotlin/.gitkeep b/examples/kotlin-cdi-jaxrs/src/test/kotlin/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/kotlin-cdi-jaxrs/src/test/resources/arquillian.xml b/examples/kotlin-cdi-jaxrs/src/test/resources/arquillian.xml new file mode 100644 index 00000000000..399f1c47320 --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/test/resources/arquillian.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/examples/kotlin-cdi-jaxrs/src/test/resources/beans.xml b/examples/kotlin-cdi-jaxrs/src/test/resources/beans.xml new file mode 100644 index 00000000000..ca873f517fb --- /dev/null +++ b/examples/kotlin-cdi-jaxrs/src/test/resources/beans.xml @@ -0,0 +1,6 @@ + + +