diff --git a/quarkus/query/demo/add-person-created-event.sh b/quarkus/query/demo/add-person-created-event.sh new file mode 100644 index 0000000..ee17e8d --- /dev/null +++ b/quarkus/query/demo/add-person-created-event.sh @@ -0,0 +1 @@ +curl -i -H 'Authorization:Basic admin:changeit' -d "@person-created-event.json" "http://127.0.0.1:2113/streams/PERSON-f645969a-402d-41a9-882b-d2d8000d0f43" -H "Content-Type:application/vnd.eventstore.events+json" diff --git a/quarkus/query/demo/person-created-event.json b/quarkus/query/demo/person-created-event.json new file mode 100644 index 0000000..3be1579 --- /dev/null +++ b/quarkus/query/demo/person-created-event.json @@ -0,0 +1,16 @@ +[ + { + "EventId":"a7b88543-ce32-40eb-a3fe-f49aec39b570", + "EventType":"PersonCreatedEvent", + "Data":{ + "event-id": "a7b88543-ce32-40eb-a3fe-f49aec39b570", + "event-timestamp": "2019-11-02T09:56:40.669Z[Etc/UTC]", + "entity-id-path": "PERSON f645969a-402d-41a9-882b-d2d8000d0f43", + "name": "Peter Parker" + }, + "MetaData":{ + "data-type":"PersonCreatedEvent", + "data-content-type":"application/json; encoding=UTF-8" + } + } +] diff --git a/quarkus/query/mvnw b/quarkus/query/mvnw new file mode 100755 index 0000000..d2f0ea3 --- /dev/null +++ b/quarkus/query/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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 + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/quarkus/query/mvnw.cmd b/quarkus/query/mvnw.cmd new file mode 100644 index 0000000..b26ab24 --- /dev/null +++ b/quarkus/query/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/quarkus/query/pom.xml b/quarkus/query/pom.xml new file mode 100644 index 0000000..cf28259 --- /dev/null +++ b/quarkus/query/pom.xml @@ -0,0 +1,180 @@ + + + + 4.0.0 + + org.fuin.cqrs4j.example.quarkus + cqrs4j-quarkus-example-query + 1.0-SNAPSHOT + + + 3.8.1 + 0.3.1-SNAPSHOT + true + 1.8 + 1.8 + UTF-8 + UTF-8 + 1.0.1.Final + quarkus-universe-bom + io.quarkus + 1.0.1.Final + 2.22.1 + + + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + + + + + + io.quarkus + quarkus-resteasy + + + + io.quarkus + quarkus-resteasy-jsonb + + + + io.quarkus + quarkus-scheduler + + + + io.quarkus + quarkus-hibernate-orm + + + + io.quarkus + quarkus-jdbc-h2 + + + + org.fuin.cqrs4j.example.quarkus + cqrs4j-quarkus-example-shared + 0.1.0-SNAPSHOT + + + logback-classic + ch.qos.logback + + + + + + org.fuin.esc + esc-eshttp + ${esc.version} + + + + + + io.quarkus + quarkus-junit5 + test + + + + io.rest-assured + rest-assured + test + + + + + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + build + + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + + + + + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + native + + + + + + diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/api/PersonResource.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/api/PersonResource.java new file mode 100644 index 0000000..dd47314 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/api/PersonResource.java @@ -0,0 +1,44 @@ +package org.fuin.cqrs4j.example.quarkus.query.api; + +import java.util.List; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.fuin.cqrs4j.example.quarkus.query.domain.QryPerson; +import org.fuin.objects4j.vo.UUIDStrValidator; + +@Path("/persons") +public class PersonResource { + + @Inject + EntityManager em; + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getAll() { + final List persons = em.createNamedQuery(QryPerson.FIND_ALL, QryPerson.class).getResultList(); + return Response.ok(persons).build(); + } + + @GET + @Path("{id}") + @Produces(MediaType.APPLICATION_JSON) + public Response getById(@PathParam("id") String id) { + if (!UUIDStrValidator.isValid(id)) { + return Response.status(Response.Status.BAD_REQUEST).entity("Invalid Person UUID").build(); + } + final QryPerson person = em.find(QryPerson.class, id); + if (person == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + return Response.ok(person).build(); + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryCheckForViewUpdatesEvent.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryCheckForViewUpdatesEvent.java new file mode 100644 index 0000000..a2d0ed9 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryCheckForViewUpdatesEvent.java @@ -0,0 +1,8 @@ +package org.fuin.cqrs4j.example.quarkus.query.app; + +/** + * Notifies the projectors to check for view updates. + */ +public class QryCheckForViewUpdatesEvent { + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryConfig.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryConfig.java new file mode 100644 index 0000000..72e59a0 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryConfig.java @@ -0,0 +1,148 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.cqrs4j.example.quarkus.query.app; + +import java.net.MalformedURLException; +import java.net.URL; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +/** + * Application configuration. + */ +@ApplicationScoped +public class QryConfig { + + private static final String EVENT_STORE_HOST = "127.0.0.1"; + + private static final int EVENT_STORE_HTTP_PORT = 2113; + + private static final int EVENT_STORE_TCP_PORT = 1113; + + private static final String EVENT_STORE_USER = "admin"; + + private static final String EVENT_STORE_PASSWORD = "changeit"; + + @Inject + @ConfigProperty(name = "EVENT_STORE_HOST", defaultValue = EVENT_STORE_HOST) + String eventStoreHost; + + @Inject + @ConfigProperty(name = "EVENT_STORE_HTTP_PORT", defaultValue = "" + EVENT_STORE_HTTP_PORT) + int eventStoreHttpPort; + + @Inject + @ConfigProperty(name = "EVENT_STORE_TCP_PORT", defaultValue = "" + EVENT_STORE_TCP_PORT) + int eventStoreTcpPort; + + @Inject + @ConfigProperty(name = "EVENT_STORE_USER", defaultValue = EVENT_STORE_USER) + String eventStoreUser; + + @Inject + @ConfigProperty(name = "EVENT_STORE_PASSWORD", defaultValue = EVENT_STORE_PASSWORD) + String eventStorePassword; + + /** + * Constructor using default values internally. + */ + public QryConfig() { + super(); + this.eventStoreHost = EVENT_STORE_HOST; + this.eventStoreHttpPort = EVENT_STORE_HTTP_PORT; + this.eventStoreTcpPort = EVENT_STORE_TCP_PORT; + this.eventStoreUser = EVENT_STORE_USER; + this.eventStorePassword = EVENT_STORE_PASSWORD; + } + + /** + * Constructor with all data. + * + * @param eventStoreHost Host. + * @param eventStoreHttpPort HTTP port + * @param eventStoreTcpPort TCP port. + * @param eventStoreUser User. + * @param eventStorePassword Password. + */ + public QryConfig(final String eventStoreHost, final int eventStoreHttpPort, final int eventStoreTcpPort, + final String eventStoreUser, final String eventStorePassword) { + super(); + this.eventStoreHost = eventStoreHost; + this.eventStoreHttpPort = eventStoreHttpPort; + this.eventStoreTcpPort = eventStoreTcpPort; + this.eventStoreUser = eventStoreUser; + this.eventStorePassword = eventStorePassword; + } + + /** + * Returns the host name of the event store. + * + * @return Name. + */ + public String getEventStoreHost() { + return eventStoreHost; + } + + /** + * Returns the HTTP port of the event store. + * + * @return Port. + */ + public int getEventStoreHttpPort() { + return eventStoreHttpPort; + } + + /** + * Returns the TCP port of the event store. + * + * @return Port. + */ + public int getEventStoreTcpPort() { + return eventStoreTcpPort; + } + + /** + * Returns the username of the event store. + * + * @return Username. + */ + public String getEventStoreUser() { + return eventStoreUser; + } + + /** + * Returns the password of the event store. + * + * @return Password. + */ + public String getEventStorePassword() { + return eventStorePassword; + } + + /** + * Creates a URL with parameters from the config. + * + * @return Event store base URL. + */ + public URL getEventStoreURL() { + try { + return new URL("http", eventStoreHost, eventStoreHttpPort, "/"); + } catch (final MalformedURLException ex) { + throw new RuntimeException("Failed to create event store URL", ex); + } + } + +} \ No newline at end of file diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryEventStoreFactory.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryEventStoreFactory.java new file mode 100644 index 0000000..b16919f --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryEventStoreFactory.java @@ -0,0 +1,65 @@ +package org.fuin.cqrs4j.example.quarkus.query.app; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.fuin.esc.api.EventStore; +import org.fuin.esc.eshttp.ESEnvelopeType; +import org.fuin.esc.eshttp.ESHttpEventStore; +import org.fuin.esc.eshttp.IESHttpEventStore; +import org.fuin.esc.spi.SerDeserializerRegistry; + +/** + * CDI factory that creates an event store connection and repositories. + */ +@ApplicationScoped +public class QryEventStoreFactory { + + /** + * Creates an event store.
+ *
+ * CAUTION: The returned event store instance is NOT thread safe. + * + * @param config + * Configuration. + * @param registry + * Serialization registry. + * + * @return Dependent scope event store. + */ + @Produces + @Dependent + public IESHttpEventStore createEventStore(final QryConfig config, final SerDeserializerRegistry registry) { + + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(config.getEventStoreUser(), + config.getEventStorePassword()); + credentialsProvider.setCredentials(AuthScope.ANY, credentials); + final ThreadFactory threadFactory = Executors.defaultThreadFactory(); + final IESHttpEventStore eventStore = new ESHttpEventStore(threadFactory, config.getEventStoreURL(), ESEnvelopeType.JSON, registry, + registry, credentialsProvider); + eventStore.open(); + return eventStore; + + } + + /** + * Closes the event store when the context is disposed. + * + * @param es + * Event store to close. + */ + public void closeEventStore(@Disposes final EventStore es) { + es.close(); + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryJsonbFactory.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryJsonbFactory.java new file mode 100644 index 0000000..657995a --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryJsonbFactory.java @@ -0,0 +1,32 @@ +package org.fuin.cqrs4j.example.quarkus.query.app; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; + +import org.eclipse.yasson.FieldAccessStrategy; +import org.fuin.cqrs4j.example.quarkus.shared.SharedUtils; + +/** + * CDI factory that creates a JSON-B instance. + */ +@ApplicationScoped +public class QryJsonbFactory { + + /** + * Creates a JSON-B instance. + * + * @return Fully configured instance. + */ + @Produces + public Jsonb createJsonb() { + final JsonbConfig config = new JsonbConfig() + .withAdapters(SharedUtils.JSONB_ADAPTERS) + .withPropertyVisibilityStrategy(new FieldAccessStrategy()); + final Jsonb jsonb = JsonbBuilder.create(config); + return jsonb; + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryScheduler.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryScheduler.java new file mode 100644 index 0000000..a003ef4 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QryScheduler.java @@ -0,0 +1,20 @@ +package org.fuin.cqrs4j.example.quarkus.query.app; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Event; +import javax.inject.Inject; + +import io.quarkus.scheduler.Scheduled; + +@ApplicationScoped +public class QryScheduler { + + @Inject + Event checkForViewUpdates; + + @Scheduled(every = "1s") + public void fireCheckForUpdatesEvent() { + checkForViewUpdates.fireAsync(new QryCheckForViewUpdatesEvent()); + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QrySerDeserializerRegistryFactory.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QrySerDeserializerRegistryFactory.java new file mode 100644 index 0000000..8b737cc --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/app/QrySerDeserializerRegistryFactory.java @@ -0,0 +1,35 @@ +package org.fuin.cqrs4j.example.quarkus.query.app; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.fuin.cqrs4j.example.quarkus.shared.SharedUtils; +import org.fuin.esc.spi.JsonbDeSerializer; +import org.fuin.esc.spi.SerDeserializerRegistry; +import org.fuin.esc.spi.SerializedDataTypeRegistry; + +/** + * CDI bean that creates a {@link SerDeserializerRegistry}. + */ +@ApplicationScoped +public class QrySerDeserializerRegistryFactory { + + @Produces + @ApplicationScoped + public SerDeserializerRegistry createRegistry() { + + // Knows about all types for usage with JSON-B + final SerializedDataTypeRegistry typeRegistry = SharedUtils.createTypeRegistry(); + + // Does the actual marshalling/unmarshalling + final JsonbDeSerializer jsonbDeSer = SharedUtils.createJsonbDeSerializer(); + + // Registry connects the type with the appropriate serializer and de-serializer + final SerDeserializerRegistry serDeserRegistry = SharedUtils.createSerDeserializerRegistry(typeRegistry, + jsonbDeSer); + + return serDeserRegistry; + + } + +} \ No newline at end of file diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/domain/QryPerson.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/domain/QryPerson.java new file mode 100644 index 0000000..75266e5 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/domain/QryPerson.java @@ -0,0 +1,103 @@ +package org.fuin.cqrs4j.example.quarkus.query.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import org.fuin.cqrs4j.example.quarkus.shared.PersonId; +import org.fuin.cqrs4j.example.quarkus.shared.PersonName; +import org.fuin.objects4j.common.Contract; + +import io.quarkus.runtime.annotations.RegisterForReflection; + +/** + * Represents a person that will be stored in the database. + */ +@Entity +@Table(name = "QRY_PERSON") +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "qry-person") +@NamedQuery(name = QryPerson.FIND_ALL, query = "SELECT p FROM QryPerson p") +@RegisterForReflection +public class QryPerson { + + public static final String FIND_ALL = "QryPerson.findAll"; + + @Id + @Column(name = "ID", nullable = false, length = 36, updatable = false) + @NotNull + @XmlElement(name = "id") + private String id; + + @Column(name = "NAME", nullable = false, length = PersonName.MAX_LENGTH, updatable = true) + @NotNull + @XmlElement(name = "name") + private String name; + + /** + * JAX-B constructor. + */ + protected QryPerson() { + super(); + } + + /** + * Constructor with all data. + * + * @param id + * Unique aggregate identifier. + * @param name + * Name of the created person + */ + public QryPerson(@NotNull final PersonId id, @NotNull final PersonName name) { + super(); + Contract.requireArgNotNull("id", id); + Contract.requireArgNotNull("name", name); + this.id = id.asString(); + this.name = name.asString(); + } + + /** + * Returns the unique person identifier. + * + * @return Aggregate ID. + */ + @NotNull + public PersonId getId() { + return PersonId.valueOf(id); + } + + /** + * Returns the name of the person to create. + * + * @return the Person name + */ + @NotNull + public PersonName getName() { + return new PersonName(name); + } + + /** + * Sets the name of the person. + * + * @param name + * Name to set. + */ + public void setName(@NotNull final PersonName name) { + Contract.requireArgNotNull("name", name); + this.name = name.asString(); + } + + @Override + public String toString() { + return "QryPerson [id=" + id + ", name=" + name + "]"; + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/domain/package-info.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/domain/package-info.java new file mode 100644 index 0000000..9633a98 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/domain/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.cqrs4j.example.quarkus.query.domain; + +/** + * Domain specific code like view objects. + */ diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/PersonCreatedEventHandler.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/PersonCreatedEventHandler.java new file mode 100644 index 0000000..c8be79d --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/PersonCreatedEventHandler.java @@ -0,0 +1,42 @@ +package org.fuin.cqrs4j.example.quarkus.query.handler; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.fuin.cqrs4j.EventHandler; +import org.fuin.cqrs4j.example.quarkus.query.domain.QryPerson; +import org.fuin.cqrs4j.example.quarkus.shared.PersonCreatedEvent; +import org.fuin.cqrs4j.example.quarkus.shared.PersonId; +import org.fuin.ddd4j.ddd.EventType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles the {@link PersonCreatedEvent}. + */ +@ApplicationScoped +public class PersonCreatedEventHandler implements EventHandler { + + private static final Logger LOG = LoggerFactory.getLogger(PersonCreatedEventHandler.class); + + @Inject + EntityManager em; + + @Override + public EventType getEventType() { + return PersonCreatedEvent.TYPE; + } + + @Override + @Transactional + public void handle(final PersonCreatedEvent event) { + LOG.info("Handle " + event.getClass().getSimpleName() + ": " + event); + final PersonId personId = event.getEntityId(); + if (em.find(QryPerson.class, personId.asString()) == null) { + em.persist(new QryPerson(personId, event.getName())); + } + } + +} \ No newline at end of file diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryEventChunkHandler.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryEventChunkHandler.java new file mode 100644 index 0000000..60cff6e --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryEventChunkHandler.java @@ -0,0 +1,50 @@ +package org.fuin.cqrs4j.example.quarkus.query.handler; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.transaction.Transactional; + +import org.fuin.cqrs4j.EventDispatcher; +import org.fuin.cqrs4j.ProjectionService; +import org.fuin.esc.api.ProjectionStreamId; +import org.fuin.esc.api.StreamEventsSlice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +@Transactional +public class QryEventChunkHandler { + + private static final Logger LOG = LoggerFactory.getLogger(QryEventChunkHandler.class); + + /** Unique name of the event store projection that is used. */ + public static final ProjectionStreamId PROJECTION_STREAM_ID = new ProjectionStreamId("qry-person-stream"); + + @Inject + EventDispatcher dispatcher; + + @Inject + ProjectionService projectionService; + + /** + * Returns the next event position to read. + * + * @return Number of the next event to read. + */ + public Long readNextEventNumber() { + return projectionService.readProjectionPosition(PROJECTION_STREAM_ID); + } + + /** + * Handles the current slice as a single transaction. + * + * @param currentSlice + * Slice with events to dispatch. + */ + public void handleChunk(final StreamEventsSlice currentSlice) { + LOG.debug("Handle chunk: {}", currentSlice); + dispatcher.dispatchCommonEvents(currentSlice.getEvents()); + projectionService.updateProjectionPosition(PROJECTION_STREAM_ID, currentSlice.getNextEventNumber()); + } + +} \ No newline at end of file diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryEventDispatcherFactory.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryEventDispatcherFactory.java new file mode 100644 index 0000000..c652e90 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryEventDispatcherFactory.java @@ -0,0 +1,21 @@ +package org.fuin.cqrs4j.example.quarkus.query.handler; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.fuin.cqrs4j.EventDispatcher; +import org.fuin.cqrs4j.SimpleEventDispatcher; + +/** + * Create an {@link EventDispatcher}. + */ +@ApplicationScoped +public class QryEventDispatcherFactory { + + @Produces + @ApplicationScoped + public EventDispatcher createDispatcher(final PersonCreatedEventHandler createdHandler) { + return new SimpleEventDispatcher(createdHandler); + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryPersonProjectionPosition.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryPersonProjectionPosition.java new file mode 100644 index 0000000..04363cb --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryPersonProjectionPosition.java @@ -0,0 +1,88 @@ +package org.fuin.cqrs4j.example.quarkus.query.handler; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; + +import org.fuin.esc.api.SimpleStreamId; +import org.fuin.esc.api.StreamId; +import org.fuin.objects4j.common.Contract; + +/** + * Stores the next position to read from the projection in the event store. + */ +@Entity +@Table(name = "QRY_PERSON_PROJECTION_POS") +public class QryPersonProjectionPosition { + + @Id + @Column(name = "STREAM_ID", nullable = false, length = 250, updatable = false) + @NotNull + private String streamId; + + @Column(name = "NEXT_POS", nullable = false, updatable = true) + @NotNull + private Long nextPos; + + /** + * JPA constructor. + */ + protected QryPersonProjectionPosition() { + super(); + } + + /** + * Constructor with mandatory data. + * + * @param streamId + * Unique stream identifier. + * @param nextPos + * Next position from the stream to read. + */ + public QryPersonProjectionPosition(@NotNull final StreamId streamId, @NotNull final Long nextPos) { + super(); + Contract.requireArgNotNull("streamId", streamId); + Contract.requireArgNotNull("nextPos", nextPos); + this.streamId = streamId.asString(); + this.nextPos = nextPos; + } + + /** + * Returns the unique stream identifier. + * + * @return Stream ID. + */ + @NotNull + public StreamId getStreamId() { + return new SimpleStreamId(streamId); + } + + /** + * Returns the next position read from the stream. + * + * @return Position to read next time. + */ + @NotNull + public Long getNextPos() { + return nextPos; + } + + /** + * Sets the next position read from the stream. + * + * @param nextPos + * New position to set. + */ + public void setNextPosition(@NotNull final Long nextPos) { + Contract.requireArgNotNull("nextPos", nextPos); + this.nextPos = nextPos; + } + + @Override + public String toString() { + return "QryPersonHandlerPosition [streamId=" + streamId + ", nextPos=" + nextPos + "]"; + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryPersonProjectionPositionRepository.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryPersonProjectionPositionRepository.java new file mode 100644 index 0000000..636d957 --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryPersonProjectionPositionRepository.java @@ -0,0 +1,53 @@ +package org.fuin.cqrs4j.example.quarkus.query.handler; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.validation.constraints.NotNull; + +import org.fuin.cqrs4j.ProjectionService; +import org.fuin.esc.api.StreamId; +import org.fuin.objects4j.common.Contract; + +/** + * Repository that contains the position of the stream. + */ +@ApplicationScoped +public class QryPersonProjectionPositionRepository implements ProjectionService { + + @Inject + EntityManager em; + + @Override + public void resetProjectionPosition(@NotNull final StreamId streamId) { + Contract.requireArgNotNull("streamId", streamId); + final QryPersonProjectionPosition pos = em.find(QryPersonProjectionPosition.class, streamId.asString()); + if (pos != null) { + pos.setNextPosition(0L); + } + } + + @Override + public Long readProjectionPosition(@NotNull StreamId streamId) { + Contract.requireArgNotNull("streamId", streamId); + final QryPersonProjectionPosition pos = em.find(QryPersonProjectionPosition.class, streamId.asString()); + if (pos == null) { + return 0L; + } + return pos.getNextPos(); + } + + @Override + public void updateProjectionPosition(@NotNull StreamId streamId, @NotNull Long nextEventNumber) { + Contract.requireArgNotNull("streamId", streamId); + Contract.requireArgNotNull("nextEventNumber", nextEventNumber); + final QryPersonProjectionPosition pos = em.find(QryPersonProjectionPosition.class, streamId.asString()); + if (pos == null) { + em.persist(new QryPersonProjectionPosition(streamId, nextEventNumber)); + } else { + pos.setNextPosition(nextEventNumber); + em.merge(pos); + } + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryProjector.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryProjector.java new file mode 100644 index 0000000..ca67c9d --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/QryProjector.java @@ -0,0 +1,89 @@ +package org.fuin.cqrs4j.example.quarkus.query.handler; + +import static org.fuin.cqrs4j.Cqrs4JUtils.tryLocked; +import static org.fuin.cqrs4j.example.quarkus.query.handler.QryEventChunkHandler.PROJECTION_STREAM_ID; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.ObservesAsync; +import javax.inject.Inject; + +import org.fuin.cqrs4j.EventDispatcher; +import org.fuin.cqrs4j.example.quarkus.query.app.QryCheckForViewUpdatesEvent; +import org.fuin.ddd4j.ddd.EventType; +import org.fuin.esc.api.EventStore; +import org.fuin.esc.api.ProjectionAdminEventStore; +import org.fuin.esc.api.TypeName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Reads incoming events from an attached event store and dispatches them to the appropriate event handlers. + */ +@ApplicationScoped +public class QryProjector { + + private static final Logger LOG = LoggerFactory.getLogger(QryProjector.class); + + /** Prevents more than one projector thread running at a time. */ + private static final Semaphore LOCK = new Semaphore(1); + + // The following beans are NOT thread safe! + // Above LOCK prevents multithreaded access + + @Inject + ProjectionAdminEventStore eventstore; + + @Inject + EventStore eventStore; + + @Inject + QryEventChunkHandler chunkHandler; + + @Inject + EventDispatcher dispatcher; + + /** + * Listens for timer events. If a second timer event occurs while the previous call is still being executed, the method will simply be + * skipped. + * + * @param event + * Timer event. + */ + public void onEvent(@ObservesAsync final QryCheckForViewUpdatesEvent event) { + tryLocked(LOCK, () -> { + try { + readStreamEvents(); + } catch (final RuntimeException ex) { + LOG.error("Error reading events from stream", ex); + } + }); + } + + private void readStreamEvents() { + + // TODO Make sure a projection with the correct events exists + // We must update the projection if new events are defined or some are removed! + if (!eventstore.projectionExists(PROJECTION_STREAM_ID)) { + final Set eventTypes = dispatcher.getAllTypes(); + final List typeNames = new ArrayList<>(); + for (final EventType eventType : eventTypes) { + typeNames.add(new TypeName(eventType.asBaseType())); + } + LOG.info("Create projection '{}' with events: {}", PROJECTION_STREAM_ID, typeNames); + eventstore.createProjection(PROJECTION_STREAM_ID, true, typeNames); + } + + // Read and dispatch events + final Long nextEventNumber = chunkHandler.readNextEventNumber(); + eventStore.readAllEventsForward(PROJECTION_STREAM_ID, nextEventNumber, 100, (currentSlice) -> { + chunkHandler.handleChunk(currentSlice); + }); + + } + +} diff --git a/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/package-info.java b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/package-info.java new file mode 100644 index 0000000..70ff56b --- /dev/null +++ b/quarkus/query/src/main/java/org/fuin/cqrs4j/example/quarkus/query/handler/package-info.java @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2015 Michael Schnell. All rights reserved. http://www.fuin.org/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library. If not, see + * http://www.gnu.org/licenses/. + */ +package org.fuin.cqrs4j.example.quarkus.query.handler; + +/** + * Code related to event handlers. + */ diff --git a/quarkus/query/src/main/resources/META-INF/resources/index.html b/quarkus/query/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000..f0c5fba --- /dev/null +++ b/quarkus/query/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,17 @@ + + + + + CQRS Quarkus Example Query Module + + +

CQRS Quarkus Example Query Module

+

This page is served by Quarkus. The source is in src/main/resources/META-INF/resources/index.html.

+ Try + +

See demo/add-person-created-event.sh and event demo/person-created-event.json to add an event

+ + diff --git a/quarkus/query/src/main/resources/application.properties b/quarkus/query/src/main/resources/application.properties new file mode 100644 index 0000000..8b7fdba --- /dev/null +++ b/quarkus/query/src/main/resources/application.properties @@ -0,0 +1,7 @@ +# Configuration file + +quarkus.datasource.url=jdbc:h2:mem:querydb +quarkus.datasource.driver=org.h2.Driver +quarkus.datasource.username=sa +quarkus.datasource.password= +quarkus.hibernate-orm.database.generation=drop-and-create diff --git a/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/NativePersonResourceIT.java b/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/NativePersonResourceIT.java new file mode 100644 index 0000000..4be9c2f --- /dev/null +++ b/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/NativePersonResourceIT.java @@ -0,0 +1,9 @@ +package org.fuin.cqrs4j.example.quarkus.query; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class NativePersonResourceIT extends PersonResourceTest { + + // Execute the same tests but in native mode. +} \ No newline at end of file diff --git a/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/PersonResourceTest.java b/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/PersonResourceTest.java new file mode 100644 index 0000000..1abbf5a --- /dev/null +++ b/quarkus/query/src/test/java/org/fuin/cqrs4j/example/quarkus/query/PersonResourceTest.java @@ -0,0 +1,20 @@ +package org.fuin.cqrs4j.example.quarkus.query; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +public class PersonResourceTest { + + @Test + public void testGetAll() { + given() + .when().get("/persons") + .then() + .statusCode(200); + } + +} \ No newline at end of file diff --git a/quarkus/shared/pom.xml b/quarkus/shared/pom.xml index 7b15152..6140c4b 100644 --- a/quarkus/shared/pom.xml +++ b/quarkus/shared/pom.xml @@ -48,7 +48,7 @@ org.fuin.esc - esc-esjc + esc-api ${esc.version}