diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.classpath b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.classpath new file mode 100644 index 000000000..9a2c80845 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.classpath @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.gitignore b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.gitignore new file mode 100644 index 000000000..744db90da --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.gitignore @@ -0,0 +1,43 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties + +# Eclipse +#.project +#.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# private stuff +src/main/resources/apple-key.txt +src/main/resources/application-dev.properties diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.project b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.project new file mode 100644 index 000000000..0d008c3ca --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/.project @@ -0,0 +1,34 @@ + + + quarkus-renarde-todo + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1669998843317 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/mvnw b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/mvnw new file mode 100644 index 000000000..a16b5431b --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/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 +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven 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.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.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/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/mvnw.cmd b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/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 https://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 Maven 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 keystroke 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.6/maven-wrapper-0.5.6.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.6/maven-wrapper-0.5.6.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/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/pom.xml b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/pom.xml new file mode 100644 index 000000000..a4dbd71bf --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/pom.xml @@ -0,0 +1,196 @@ + + + 4.0.0 + io.quarkus.example + renarde-todo + 1.0.0-SNAPSHOT + + 3.8.1 + true + 11 + 11 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus + 2.9.2.Final + 3.0.0-M5 + 1.0.2 + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + org.webjars + bootstrap + 5.1.3 + + + org.webjars.npm + bootstrap-icons + 1.7.0 + + + io.quarkus + quarkus-webjars-locator + + + io.quarkiverse.renarde + quarkus-renarde + ${renarde.version} + + + io.quarkiverse.renarde + quarkus-renarde-oidc + ${renarde.version} + + + io.quarkiverse.renarde + quarkus-renarde-oidc-tests + ${renarde.version} + + + io.quarkus + quarkus-mailer + + + io.quarkus + quarkus-oidc + + + io.quarkus + quarkus-elytron-security-common + + + io.quarkus + quarkus-smallrye-jwt-build + + + io.quarkus + quarkus-hibernate-orm-panache + + + io.quarkus + quarkus-rest-client-reactive + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-jdbc-postgresql + + + io.quarkus + quarkus-security-webauthn + + + io.quarkus + quarkus-test-security-webauthn + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + com.github.tomakehurst + wiremock + 2.27.2 + test + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + ${maven.compiler.parameters} + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.jvm b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.jvm new file mode 100644 index 000000000..e8f9a8a57 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.jvm @@ -0,0 +1,55 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/aviouf-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/aviouf-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/aviouf-jvm +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 + +ARG JAVA_PACKAGE=java-11-openjdk-headless +ARG RUN_JAVA_VERSION=1.3.8 +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' +# Install java and the run-java script +# Also set up permissions for user `1001` +RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ + && microdnf update \ + && microdnf clean all \ + && mkdir /deployments \ + && chown 1001 /deployments \ + && chmod "g+rwX" /deployments \ + && chown 1001:root /deployments \ + && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ + && chown 1001 /deployments/run-java.sh \ + && chmod 540 /deployments/run-java.sh \ + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security + +# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=1001 target/quarkus-app/*.jar /deployments/ +COPY --chown=1001 target/quarkus-app/app/ /deployments/app/ +COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT [ "/deployments/run-java.sh" ] + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.legacy-jar b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.legacy-jar new file mode 100644 index 000000000..3203b329c --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.legacy-jar @@ -0,0 +1,51 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package -Dquarkus.package.type=legacy-jar +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/aviouf-legacy-jar . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/aviouf-legacy-jar +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/aviouf-legacy-jar +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 + +ARG JAVA_PACKAGE=java-11-openjdk-headless +ARG RUN_JAVA_VERSION=1.3.8 +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' +# Install java and the run-java script +# Also set up permissions for user `1001` +RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ + && microdnf update \ + && microdnf clean all \ + && mkdir /deployments \ + && chown 1001 /deployments \ + && chmod "g+rwX" /deployments \ + && chown 1001:root /deployments \ + && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ + && chown 1001 /deployments/run-java.sh \ + && chmod 540 /deployments/run-java.sh \ + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security + +# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/app.jar + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT [ "/deployments/run-java.sh" ] diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.native b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.native new file mode 100644 index 000000000..ffd72f27c --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.native @@ -0,0 +1,27 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode +# +# Before building the container image run: +# +# ./mvnw package -Pnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/aviouf . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/aviouf +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.native-distroless b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.native-distroless new file mode 100644 index 000000000..ec6a5101a --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/docker/Dockerfile.native-distroless @@ -0,0 +1,23 @@ +#### +# This Dockerfile is used in order to build a distroless container that runs the Quarkus application in native (no JVM) mode +# +# Before building the container image run: +# +# ./mvnw package -Pnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-distroless -t quarkus/aviouf . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/aviouf +# +### +FROM quay.io/quarkus/quarkus-distroless-image:1.0 +COPY target/*-runner /application + +EXPOSE 8080 +USER nonroot + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/email/Emails.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/email/Emails.java new file mode 100644 index 000000000..0f6d3f78f --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/email/Emails.java @@ -0,0 +1,24 @@ +package email; + +import io.quarkus.mailer.MailTemplate.MailTemplateInstance; +import io.quarkus.qute.CheckedTemplate; +import model.User; + +public class Emails { + + private static final String FROM = "Todos "; + private static final String SUBJECT_PREFIX = "[Todos] "; + + @CheckedTemplate + static class Templates { + public static native MailTemplateInstance confirm(User user); + } + + public static void confirm(User user) { + Templates.confirm(user) + .subject(SUBJECT_PREFIX + "Please confirm your email address") + .to(user.email) + .from(FROM) + .send().await().indefinitely(); + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/Todo.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/Todo.java new file mode 100644 index 000000000..2510978ec --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/Todo.java @@ -0,0 +1,26 @@ +package model; + +import java.util.Date; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +public class Todo extends PanacheEntity { + + @ManyToOne + public User owner; + + public String task; + + public boolean done; + + public Date doneDate; + + public static List findByOwner(User user) { + return find("owner = ?1 ORDER BY id", user).list(); + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/User.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/User.java new file mode 100644 index 000000000..2c44559dd --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/User.java @@ -0,0 +1,99 @@ +package model; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import io.quarkiverse.renarde.oidc.RenardeUser; +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +@Table(name = "user_table", uniqueConstraints = @UniqueConstraint(columnNames = {"tenantId", "authId"})) +public class User extends PanacheEntity implements RenardeUser { + + @Column(nullable = false) + public String email; + @Column(unique = true) + public String userName; + public String password; + // non-owning side, so we can add more credentials later + @OneToOne(mappedBy = "user") + public WebAuthnCredential webAuthnCredential; + public String firstName; + public String lastName; + public boolean isAdmin; + + @Column(unique = true) + public String confirmationCode; + + public String tenantId; + public String authId; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + public UserStatus status; + + public boolean isRegistered(){ + return status == UserStatus.REGISTERED; + } + + @Override + public Set getRoles() { + Set roles = new HashSet<>(); + if(isAdmin) { + roles.add("admin"); + } + return roles; + } + + @Override + public String getUserId() { + return userName; + } + + public boolean isOidc() { + return tenantId != null; + } + + private boolean isWebAuthn() { + return webAuthnCredential != null; + } + + public String getIconType() { + if(isOidc()) + return "shield-check"; + if(isWebAuthn()) + return "fingerprint"; + return "shield-lock"; + } + + // + // Helpers + + public static User findUnconfirmedByEmail(String email) { + return find("LOWER(email) = ?1 AND status = ?2", email.toLowerCase(), UserStatus.CONFIRMATION_REQUIRED).firstResult(); + } + + public static User findRegisteredByUserName(String username) { + return find("LOWER(userName) = ?1 AND status = ?2", username.toLowerCase(), UserStatus.REGISTERED).firstResult(); + } + + public static User findByUserName(String username) { + return find("LOWER(userName) = ?1", username.toLowerCase()).firstResult(); + } + + public static User findByAuthId(String tenantId, String authId) { + return find("tenantId = ?1 AND authId = ?2", tenantId, authId).firstResult(); + } + + public static User findForContirmation(String confirmationCode) { + return find("confirmationCode = ?1 AND status = ?2", confirmationCode, UserStatus.CONFIRMATION_REQUIRED).firstResult(); + } +} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/UserStatus.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/UserStatus.java new file mode 100644 index 000000000..c8e8caded --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/UserStatus.java @@ -0,0 +1,5 @@ +package model; + +public enum UserStatus { + REGISTERED, CONFIRMATION_REQUIRED; +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/WebAuthnCertificate.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/WebAuthnCertificate.java new file mode 100644 index 000000000..d03eb2aed --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/WebAuthnCertificate.java @@ -0,0 +1,18 @@ +package model; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +public class WebAuthnCertificate extends PanacheEntity { + + @ManyToOne + public WebAuthnCredential webAuthnCredential; + + /** + * The list of X509 certificates encoded as base64url. + */ + public String x5c; +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/WebAuthnCredential.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/WebAuthnCredential.java new file mode 100644 index 000000000..3fbe263e8 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/model/WebAuthnCredential.java @@ -0,0 +1,107 @@ +package model; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.vertx.ext.auth.webauthn.Authenticator; +import io.vertx.ext.auth.webauthn.PublicKeyCredential; + +@Entity +public class WebAuthnCredential extends PanacheEntityBase { + + /** + * The username linked to this authenticator + */ + public String userName; + + /** + * The type of key (must be "public-key") + */ + public String type = "public-key"; + + /** + * The non user identifiable id for the authenticator + */ + @Id + public String credID; + + /** + * The public key associated with this authenticator + */ + public String publicKey; + + /** + * The signature counter of the authenticator to prevent replay attacks + */ + public long counter; + + public String aaguid; + + /** + * The Authenticator attestation certificates object, a JSON like: + *
{@code
+     *   {
+     *     "alg": "string",
+     *     "x5c": [
+     *       "base64"
+     *     ]
+     *   }
+     * }
+ */ + /** + * The algorithm used for the public credential + */ + public PublicKeyCredential alg; + + /** + * The list of X509 certificates encoded as base64url. + */ + @OneToMany(mappedBy = "webAuthnCredential") + public List x5c = new ArrayList<>(); + + public String fmt; + + // this is the owning side + @OneToOne + public User user; + + public WebAuthnCredential() { + } + + public WebAuthnCredential(Authenticator authenticator, User user) { + aaguid = authenticator.getAaguid(); + if(authenticator.getAttestationCertificates() != null) + alg = authenticator.getAttestationCertificates().getAlg(); + counter = authenticator.getCounter(); + credID = authenticator.getCredID(); + fmt = authenticator.getFmt(); + publicKey = authenticator.getPublicKey(); + type = authenticator.getType(); + userName = authenticator.getUserName(); + if(authenticator.getAttestationCertificates() != null + && authenticator.getAttestationCertificates().getX5c() != null) { + for (String x5c : authenticator.getAttestationCertificates().getX5c()) { + WebAuthnCertificate cert = new WebAuthnCertificate(); + cert.x5c = x5c; + cert.webAuthnCredential = this; + this.x5c.add(cert); + } + } + user.webAuthnCredential = this; + this.user = user; + } + + public static List findByUserId(String userId) { + return list("userName", userId); + } + + public static WebAuthnCredential findByCredId(String credId) { + return findById(credId); + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Application.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Application.java new file mode 100644 index 000000000..9a7363efc --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Application.java @@ -0,0 +1,34 @@ +package rest; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import io.quarkiverse.renarde.Controller; +import io.quarkus.qute.CheckedTemplate; +import io.quarkus.qute.TemplateInstance; +import io.smallrye.common.annotation.Blocking; + +@Blocking +public class Application extends Controller { + + @CheckedTemplate + static class Templates { + public static native TemplateInstance index(); + public static native TemplateInstance about(); + } + + @Path("/") + public TemplateInstance index() { + return Templates.index(); + } + + @Path("/about") + public TemplateInstance about() { + return Templates.about(); + } + + @POST + public void test() { + + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Login.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Login.java new file mode 100644 index 000000000..563362fdb --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Login.java @@ -0,0 +1,232 @@ +package rest; + +import java.util.UUID; + +import javax.inject.Inject; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.ws.rs.BeanParam; +import javax.ws.rs.POST; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.hibernate.validator.constraints.Length; +import org.jboss.resteasy.reactive.RestForm; +import org.jboss.resteasy.reactive.RestPath; +import org.jboss.resteasy.reactive.RestQuery; + +import email.Emails; +import io.quarkiverse.renarde.oidc.ControllerWithUser; +import io.quarkiverse.renarde.oidc.RenardeSecurity; +import io.quarkiverse.renarde.router.Router; +import io.quarkiverse.renarde.util.StringUtils; +import io.quarkus.elytron.security.common.BcryptUtil; +import io.quarkus.qute.CheckedTemplate; +import io.quarkus.qute.TemplateInstance; +import io.quarkus.security.Authenticated; +import io.quarkus.security.webauthn.WebAuthnLoginResponse; +import io.quarkus.security.webauthn.WebAuthnRegisterResponse; +import io.quarkus.security.webauthn.WebAuthnSecurity; +import io.smallrye.common.annotation.Blocking; +import io.vertx.ext.auth.webauthn.Authenticator; +import io.vertx.ext.web.RoutingContext; +import model.User; +import model.UserStatus; +import model.WebAuthnCredential; + +@Blocking +public class Login extends ControllerWithUser { + @Inject + RenardeSecurity security; + + @Inject + WebAuthnSecurity webAuthnSecurity; + + @CheckedTemplate + static class Templates { + public static native TemplateInstance login(); + public static native TemplateInstance register(String email); + public static native TemplateInstance confirm(User newUser); + public static native TemplateInstance logoutFirst(); + public static native TemplateInstance welcome(); + } + + /** + * Login page + */ + public TemplateInstance login() { + return Templates.login(); + } + + /** + * Welcome page at the end of registration + */ + @Authenticated + public TemplateInstance welcome() { + return Templates.welcome(); + } + + /** + * Manual login form + */ + @POST + public Response manualLogin(@NotBlank @RestForm String userName, + @RestForm String password, + @BeanParam WebAuthnLoginResponse webAuthnResponse, + RoutingContext ctx) { + if(webAuthnResponse.isSet()) { + validation.required("webAuthnId", webAuthnResponse.webAuthnId); + validation.required("webAuthnRawId", webAuthnResponse.webAuthnRawId); + validation.required("webAuthnResponseClientDataJSON", webAuthnResponse.webAuthnResponseClientDataJSON); + validation.required("webAuthnResponseAuthenticatorData", webAuthnResponse.webAuthnResponseAuthenticatorData); + validation.required("webAuthnResponseSignature", webAuthnResponse.webAuthnResponseSignature); + // UserHandle not required + validation.required("webAuthnType", webAuthnResponse.webAuthnType); + } else { + validation.required("password", password); + } + if(validationFailed()) { + login(); + } + User user = User.findRegisteredByUserName(userName); + if(user == null) { + validation.addError("userName", "Invalid username/pasword"); + prepareForErrorRedirect(); + login(); + } + if(!webAuthnResponse.isSet()) { + if(!BcryptUtil.matches(password, user.password)) { + validation.addError("userName", "Invalid username/pasword"); + prepareForErrorRedirect(); + login(); + } + } else { + // This is sync anyway + Authenticator authenticator = this.webAuthnSecurity.login(webAuthnResponse, ctx) + .await().indefinitely(); + // bump the auth counter + user.webAuthnCredential.counter = authenticator.getCounter(); + } + NewCookie cookie = security.makeUserCookie(user); + return Response.seeOther(Router.getURI(Application::index)).cookie(cookie).build(); + } + + /** + * Manual registration form, sends confirmation email + */ + @POST + public TemplateInstance register(@RestForm @NotBlank @Email String email) { + if(validationFailed()) + login(); + User newUser = User.findUnconfirmedByEmail(email); + if(newUser == null) { + newUser = new User(); + newUser.email = email; + newUser.status = UserStatus.CONFIRMATION_REQUIRED; + newUser.confirmationCode = UUID.randomUUID().toString(); + newUser.persist(); + } + // send the confirmation code again + Emails.confirm(newUser); + return Templates.register(email); + } + + + /** + * Confirmation form, once email is verified, to add user info + */ + public TemplateInstance confirm(@RestPath String confirmationCode){ + checkLogoutFirst(); + User newUser = checkConfirmationCode(confirmationCode); + return Templates.confirm(newUser); + } + + private void checkLogoutFirst() { + if(getUser() != null) { + logoutFirst(); + } + } + + /** + * Link to logout page + */ + public TemplateInstance logoutFirst() { + return Templates.logoutFirst(); + } + + private User checkConfirmationCode(String confirmationCode) { + // can't use error reporting as those are query parameters and not form fields + if(StringUtils.isEmpty(confirmationCode)){ + flash("message", "Missing confirmation code"); + flash("messageType", "error"); + redirect(Application.class).index(); + } + User user = User.findForContirmation(confirmationCode); + if(user == null){ + flash("message", "Invalid confirmation code"); + flash("messageType", "error"); + redirect(Application.class).index(); + } + return user; + } + + @POST + public Response complete(@RestQuery String confirmationCode, + @RestForm @NotBlank String userName, + @RestForm @Length(min = 8) String password, + @RestForm @Length(min = 8) String password2, + @BeanParam WebAuthnRegisterResponse webAuthnResponse, + @RestForm @NotBlank String firstName, + @RestForm @NotBlank String lastName, + RoutingContext ctx) { + checkLogoutFirst(); + User user = checkConfirmationCode(confirmationCode); + + if(validationFailed()) + confirm(confirmationCode); + + // is it OIDC? + if(!user.isOidc()) { + if(!webAuthnResponse.isSet()) { + validation.required("password", password); + validation.required("password2", password2); + validation.equals("password", password, password2); + } else { + validation.required("webAuthnId", webAuthnResponse.webAuthnId); + validation.required("webAuthnRawId", webAuthnResponse.webAuthnRawId); + validation.required("webAuthnResponseAttestationObject", webAuthnResponse.webAuthnResponseAttestationObject); + validation.required("webAuthnResponseClientDataJSON", webAuthnResponse.webAuthnResponseClientDataJSON); + validation.required("webAuthnType", webAuthnResponse.webAuthnType); + } + } + + if(User.findRegisteredByUserName(userName) != null) + validation.addError("userName", "User name already taken"); + if(validationFailed()) + confirm(confirmationCode); + + if(!user.isOidc()) { + if(!webAuthnResponse.isSet()) { + user.password = BcryptUtil.bcryptHash(password); + } else { + // this is sync + Authenticator authenticator = webAuthnSecurity.register(webAuthnResponse, ctx).await().indefinitely(); + WebAuthnCredential creds = new WebAuthnCredential(authenticator, user); + creds.persist(); + } + } + user.userName = userName; + user.firstName = firstName; + user.lastName = lastName; + user.confirmationCode = null; + user.status = UserStatus.REGISTERED; + + ResponseBuilder responseBuilder = Response.seeOther(Router.getURI(Login::welcome)); + if(!user.isOidc()) { + NewCookie cookie = security.makeUserCookie(user); + responseBuilder.cookie(cookie); + } + return responseBuilder.build(); + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Templates.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Templates.java new file mode 100644 index 000000000..8c03d0ab0 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Templates.java @@ -0,0 +1,9 @@ +package rest; + +import io.quarkus.qute.CheckedTemplate; +import io.quarkus.qute.TemplateInstance; + +@CheckedTemplate +public class Templates { + public static native TemplateInstance main(); +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Todos.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Todos.java new file mode 100644 index 000000000..d9ddcf2e3 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/rest/Todos.java @@ -0,0 +1,71 @@ +package rest; + +import java.util.Date; +import java.util.List; + +import javax.validation.constraints.NotBlank; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import org.jboss.resteasy.reactive.RestForm; +import org.jboss.resteasy.reactive.RestPath; + +import io.quarkiverse.renarde.oidc.ControllerWithUser; +import io.quarkus.qute.CheckedTemplate; +import io.quarkus.qute.TemplateInstance; +import io.quarkus.security.Authenticated; +import io.smallrye.common.annotation.Blocking; +import model.Todo; +import model.User; + +@Blocking +@Authenticated +public class Todos extends ControllerWithUser { + + @CheckedTemplate + static class Templates { + public static native TemplateInstance index(List todos); + } + + public TemplateInstance index() { + List todos = Todo.findByOwner(getUser()); + return Templates.index(todos); + } + + @POST + public void delete(@RestPath Long id) { + Todo todo = Todo.findById(id); + notFoundIfNull(todo); + if(todo.owner != getUser()) + notFound(); + todo.delete(); + flash("message", "Task deleted"); + index(); + } + + @POST + public void done(@RestPath Long id) { + Todo todo = Todo.findById(id); + notFoundIfNull(todo); + if(todo.owner != getUser()) + notFound(); + todo.done = !todo.done; + if(todo.done) + todo.doneDate = new Date(); + flash("message", "Task updated"); + index(); + } + + @POST + public void add(@NotBlank @RestForm String task) { + if(validationFailed()) { + index(); + } + Todo todo = new Todo(); + todo.task = task; + todo.owner = getUser(); + todo.persist(); + flash("message", "Task added"); + index(); + } +} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Globals.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Globals.java new file mode 100644 index 000000000..7ffb8961c --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Globals.java @@ -0,0 +1,16 @@ +package util; + +import javax.ws.rs.core.UriInfo; + +import io.quarkus.arc.Arc; +import io.quarkus.qute.TemplateGlobal; + +@TemplateGlobal +public class Globals { + public static String requestUrl() { + return Arc.container().instance(UriInfo.class).get().getRequestUri().toASCIIString(); + } + public static int VARCHAR_SIZE() { + return Util.VARCHAR_SIZE; + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/JavaExtensions.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/JavaExtensions.java new file mode 100644 index 000000000..e2ceb4a62 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/JavaExtensions.java @@ -0,0 +1,20 @@ +package util; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import io.quarkus.qute.TemplateExtension; + +@TemplateExtension +public class JavaExtensions { + + public static boolean isRecent(Date date){ + Date now = new Date(); + Calendar cal = new GregorianCalendar(); + cal.add(Calendar.MONTH, -6); + Date sixMonthsAgo = cal.getTime(); + return date.before(now) && date.after(sixMonthsAgo); + } + +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/MyOidcSetup.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/MyOidcSetup.java new file mode 100644 index 000000000..f78144bf5 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/MyOidcSetup.java @@ -0,0 +1,95 @@ +package util; + +import java.net.URI; +import java.util.UUID; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.transaction.Transactional; +import javax.ws.rs.core.Response; + +import io.quarkiverse.renarde.oidc.RenardeOidcHandler; +import io.quarkiverse.renarde.oidc.RenardeSecurity; +import io.quarkiverse.renarde.oidc.RenardeUser; +import io.quarkiverse.renarde.oidc.RenardeUserProvider; +import io.quarkiverse.renarde.router.Router; +import io.quarkiverse.renarde.util.Flash; +import io.quarkiverse.renarde.util.RedirectException; +import model.User; +import model.UserStatus; +import rest.Application; +import rest.Login; + +@ApplicationScoped +public class MyOidcSetup implements RenardeUserProvider, RenardeOidcHandler { + + @Inject + RenardeSecurity security; + + @Inject + Flash flash; + + @Override + public RenardeUser findUser(String tenantId, String id) { + if(tenantId == null || tenantId.equals("manual")) { + return User.findByUserName(id); + } else { + return User.findByAuthId(tenantId, id); + } + } + + @Transactional + @Override + public void oidcSuccess(String tenantId, String authId) { + User user = User.findByAuthId(tenantId, authId); + URI uri; + if(user == null) { + // registration + user = new User(); + user.tenantId = tenantId; + user.authId = authId; + + user.email = security.getOidcEmail(); + // workaround for Twitter + if(user.email == null) + user.email = "twitter@example.com"; + user.firstName = security.getOidcFirstName(); + user.lastName = security.getOidcLastName(); + user.userName = security.getOidcUserName(); + + user.status = UserStatus.CONFIRMATION_REQUIRED; + user.confirmationCode = UUID.randomUUID().toString(); + user.persist(); + + // go to registration + uri = Router.getURI(Login::confirm, user.confirmationCode); + } else if(!user.isRegistered()) { + // user exists, but not fully registered yet + // go to registration + uri = Router.getURI(Login::confirm, user.confirmationCode); + } else { + // regular login + uri = Router.getURI(Application::index); + } + throw new RedirectException(Response.seeOther(uri).build()); + } + + @Override + public void loginWithOidcSession(String tenantId, String authId) { + RenardeUser user = findUser(tenantId, authId); + // old cookie, no such user + if(user == null) { + flash.flash("message", "Invalid user: "+authId); + throw new RedirectException(security.makeLogoutResponse()); + } + // redirect to registration + URI uri; + if(!user.isRegistered()) { + uri = Router.getURI(Login::confirm, ((User)user).confirmationCode); + } else { + flash.flash("message", "Already logged in"); + uri = Router.getURI(Application::index); + } + throw new RedirectException(Response.seeOther(uri).build()); + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/MyWebAuthnSetup.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/MyWebAuthnSetup.java new file mode 100644 index 000000000..d2168e3d4 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/MyWebAuthnSetup.java @@ -0,0 +1,70 @@ +package util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.transaction.Transactional; + +import io.quarkus.security.webauthn.WebAuthnUserProvider; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.auth.webauthn.AttestationCertificates; +import io.vertx.ext.auth.webauthn.Authenticator; +import model.WebAuthnCertificate; +import model.WebAuthnCredential; + +@Blocking +@ApplicationScoped +public class MyWebAuthnSetup implements WebAuthnUserProvider { + + @Transactional + @Override + public Uni> findWebAuthnCredentialsByUserName(String userId) { + return Uni.createFrom().item(toAuthenticators(WebAuthnCredential.findByUserId(userId))); + } + + @Transactional + @Override + public Uni> findWebAuthnCredentialsByCredID(String credId) { + WebAuthnCredential creds = WebAuthnCredential.findByCredId(credId); + if(creds == null) + return Uni.createFrom().item(Collections.emptyList()); + return Uni.createFrom().item(Arrays.asList(toAuthenticator(creds))); + } + + @Override + public Uni updateOrStoreWebAuthnCredentials(Authenticator authenticator) { + // do nothing here, it's done in login/register + return Uni.createFrom().nullItem(); + } + + private static List toAuthenticators(List dbs) { + List ret = new ArrayList<>(dbs.size()); + for (WebAuthnCredential db : dbs) { + ret.add(toAuthenticator(db)); + } + return ret; + } + + private static Authenticator toAuthenticator(WebAuthnCredential credential) { + Authenticator ret = new Authenticator(); + ret.setAaguid(credential.aaguid); + AttestationCertificates attestationCertificates = new AttestationCertificates(); + attestationCertificates.setAlg(credential.alg); + List x5cs = new ArrayList<>(credential.x5c.size()); + for (WebAuthnCertificate webAuthnCertificate : credential.x5c) { + x5cs.add(webAuthnCertificate.x5c); + } + ret.setAttestationCertificates(attestationCertificates); + ret.setCounter(credential.counter); + ret.setCredID(credential.credID); + ret.setFmt(credential.fmt); + ret.setPublicKey(credential.publicKey); + ret.setType(credential.type); + ret.setUserName(credential.userName); + return ret; + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Startup.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Startup.java new file mode 100644 index 000000000..c20ef4d71 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Startup.java @@ -0,0 +1,40 @@ +package util; + +import java.util.Date; + +import javax.enterprise.event.Observes; +import javax.transaction.Transactional; + +import io.quarkus.elytron.security.common.BcryptUtil; +import io.quarkus.runtime.StartupEvent; +import model.Todo; +import model.User; +import model.UserStatus; + +public class Startup { + @Transactional + public void onStartup(@Observes StartupEvent start) { + System.err.println("Adding user fromage"); + User stef = new User(); + stef.email = "fromage@example.com"; + stef.firstName = "Stef"; + stef.lastName = "Epardaud"; + stef.userName = "fromage"; + stef.password = BcryptUtil.bcryptHash("1q2w3e4r"); + stef.status = UserStatus.REGISTERED; + stef.isAdmin = true; + stef.persist(); + + Todo todo = new Todo(); + todo.owner = stef; + todo.task = "Buy cheese"; + todo.done = true; + todo.doneDate = new Date(); + todo.persist(); + + todo = new Todo(); + todo.owner = stef; + todo.task = "Eat cheese"; + todo.persist(); + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Util.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Util.java new file mode 100644 index 000000000..3febe0ada --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/java/util/Util.java @@ -0,0 +1,7 @@ +package util; + +public class Util { + + public static final int VARCHAR_SIZE = 255; + +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/Apple-key-dev.p8 b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/Apple-key-dev.p8 new file mode 100644 index 000000000..1efa33177 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/Apple-key-dev.p8 @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgQNNVeHUtGDGEJGEM +UcCzTr4DC8Yf9NJ/LrXmU6LJHQmhRANCAATDS9rjbOzoeEt+lHlbaWnBXIdqlZBn +gIT+qieWnhWyIaf1pTIbGp2AI0shGMSzU+zflug7Gy+BkQExbihnP3Ly +-----END PRIVATE KEY----- diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-apple.svg b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-apple.svg new file mode 100644 index 000000000..73630c9ba --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-facebook.svg b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-facebook.svg new file mode 100644 index 000000000..7382e4681 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-facebook.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-github.svg b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-github.svg new file mode 100644 index 000000000..cdafcd125 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-github.svg @@ -0,0 +1,51 @@ + +image/svg+xml diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-google.svg b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-google.svg new file mode 100644 index 000000000..c652a5a18 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-google.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-microsoft.svg b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-microsoft.svg new file mode 100644 index 000000000..5334aa7ca --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-microsoft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-twitter.svg b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-twitter.svg new file mode 100644 index 000000000..b60552810 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/signin-twitter.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/webauthn.svg b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/webauthn.svg new file mode 100644 index 000000000..0dde24a44 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/images/webauthn.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/javascripts/main.js b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/javascripts/main.js new file mode 100644 index 000000000..d4bec2c7c --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/javascripts/main.js @@ -0,0 +1,35 @@ +// INSERT YOUR JS HERE + +function clearValidationError(inputElement) { + // remove any lingering error + const previousError = inputElement.parentElement.querySelector(".invalid-feedback"); + if(previousError) { + previousError.remove(); + } +} + +function addValidationError(inputElement, error){ + clearValidationError(inputElement); + inputElement.classList.add('is-invalid'); + // add ​error + const span = document.createElement("span"); + span.classList.add("invalid-feedback"); + span.append(error); + inputElement.parentElement.append(span); +} + +function requireField(name){ + const field = document.getElementById(name); + clearValidationError(field); + if(!field.value || field.value.length == 0) { + addValidationError(field, "must not be blank"); + return Promise.reject("must not be blank"); + } else { + return Promise.resolve(field.value); + } +} + +function requireFields(...args){ + return Promise.all(args.map(arg => requireField(arg))); +} + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/stylesheets/main.css b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/stylesheets/main.css new file mode 100644 index 000000000..e12023d97 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/META-INF/resources/stylesheets/main.css @@ -0,0 +1,4 @@ +/* INSERT YOUR STYLE HERE */ +.inline { + display: inline; +} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/application-test.properties b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/application-test.properties new file mode 100644 index 000000000..fbc1c484f --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/application-test.properties @@ -0,0 +1,33 @@ +# Google +quarkus.oidc.google.client-id=GGLCLIENT +quarkus.oidc.google.credentials.secret=GGLSECRET + +# Github +quarkus.oidc.github.client-id=GHCLIENT +# FIXME: must be long otherwise we get an exception: +#io.smallrye.jwt.build.JwtSignatureException: SRJWT05012: Failure to create a signed JWT token: A key of the same size as the hash output (i.e. 256 bits for HS256) or larger MUST be used with the HMAC SHA algorithms but this key is only 64 bits +# at io.smallrye.jwt.build.impl.JwtSignatureImpl.signInternal(JwtSignatureImpl.java:150) +# at io.smallrye.jwt.build.impl.JwtSignatureImpl.sign(JwtSignatureImpl.java:50) +# at io.quarkus.oidc.runtime.CodeAuthenticationMechanism.generateInternalIdToken(CodeAuthenticationMechanism.java:406) +quarkus.oidc.github.credentials.secret=GHSECRETGHSECRETGHSECRETGHSECRET + +# Twitter +quarkus.oidc.twitter.client-id=TWCLIENT +quarkus.oidc.twitter.credentials.secret=TWSECRETTWSECRETTWSECRETTWSECRET + +# MS +quarkus.oidc.microsoft.client-id=MSCLIENT +quarkus.oidc.microsoft.credentials.secret=MSSECRET + +# Facebook +quarkus.oidc.facebook.client-id=FBCLIENT +quarkus.oidc.facebook.credentials.secret=FBSECRETFBSECRETFBSECRETFBSECRETFBSECRET + +# Apple +quarkus.oidc.apple.client-id=APLCLIENT +#set by mock +#quarkus.oidc.apple.credentials.jwt.key-file=apple-key.txt +quarkus.oidc.apple.credentials.jwt.token-key-id=APLKEYID +quarkus.oidc.apple.credentials.jwt.issuer=APLISSUER +quarkus.oidc.apple.credentials.jwt.subject=APLSUBJECT + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/application.properties b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/application.properties new file mode 100644 index 000000000..75fd572d9 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/application.properties @@ -0,0 +1,51 @@ + +# Twitter +quarkus.oidc.twitter.provider=twitter +quarkus.oidc.twitter.client-id=SECRET +quarkus.oidc.twitter.credentials.secret=SECRETSECRETSECRETSECRETSECRETSECRET + +# Google +quarkus.oidc.google.provider=google +quarkus.oidc.google.client-id=SECRET +quarkus.oidc.google.credentials.secret=SECRET + +# Github +quarkus.oidc.github.provider=github +quarkus.oidc.github.client-id=SECRET +quarkus.oidc.github.credentials.secret=SECRET + +# MS +quarkus.oidc.microsoft.provider=microsoft +quarkus.oidc.microsoft.client-id=SECRET +quarkus.oidc.microsoft.credentials.secret=SECRET + +# Facebook +quarkus.oidc.facebook.provider=facebook +quarkus.oidc.facebook.client-id=SECRET +quarkus.oidc.facebook.credentials.secret=SECRET + +# Apple +quarkus.oidc.apple.provider=apple +quarkus.oidc.apple.client-id=SECRET +quarkus.oidc.apple.credentials.jwt.key-file=Apple-key-dev.p8 +# this actually needs to be set for Apple keys, but not the fake dev one +#quarkus.oidc.apple.credentials.jwt.token-key-id=SECRET +quarkus.oidc.apple.credentials.jwt.issuer=SECRET +quarkus.oidc.apple.credentials.jwt.subject=SECRET + +# Manual context +quarkus.oidc.manual.tenant-enabled=false + +# Default is just disabled +quarkus.oidc.tenant-enabled=false + +# Get rid of keycloak +quarkus.keycloak.devservices.enabled=false + +# can't seem to set it from Renarde because it's a build time config +quarkus.http.auth.proactive=false + +quarkus.log.category."io.netty.handler.logging.LoggingHandler".level=DEBUG +quarkus.log.category."io.quarkus.oidc.runtime".level=DEBUG + +quarkus.webauthn.login-page=/Login/login diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/imports.sql b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/imports.sql new file mode 100644 index 000000000..e69de29bb diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Application/about.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Application/about.html new file mode 100644 index 000000000..1628f6053 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Application/about.html @@ -0,0 +1,24 @@ +{#include main.html } +{#title}About Todos{/title} + +
+
+

Super cool

+

+ Yay!. +

+
+
+

Super cool

+

+ Yay!. +

+
+
+

Super cool

+

+ Yay!. +

+
+
+{/include} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Application/index.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Application/index.html new file mode 100644 index 000000000..0e6f17676 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Application/index.html @@ -0,0 +1,11 @@ +{#include main.html } +{#title}Welcome to Todos{/title} + +
+
+

Welcome to Todos

+

Start adding todos today!

+
+
+ +{/include} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Emails/confirm.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Emails/confirm.html new file mode 100644 index 000000000..309139fb8 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Emails/confirm.html @@ -0,0 +1,18 @@ +{#include email.html } + +

+ Welcome to Todos. +

+ +

+ You received this email because someone (hopefully you) wants to register on Todos. +

+ +

+ If you don't want to register, you can safely ignore this email. +

+ +

+ If you want to register, complete your registration. +

+{/include} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Emails/confirm.txt b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Emails/confirm.txt new file mode 100644 index 000000000..c775b32ed --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Emails/confirm.txt @@ -0,0 +1,12 @@ +{#include email.txt} + +Welcome to Todos. + +You received this email because someone (hopefully you) wants to register on Todos. + +If you don't want to register, you can safely ignore this email. + +If you want to register, complete your registration by going to the following address: + +{uriabs:Login.confirm(user.confirmationCode)} +{/include} diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/confirm.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/confirm.html new file mode 100644 index 000000000..f26144344 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/confirm.html @@ -0,0 +1,74 @@ +{#include main.html } +{#title}Complete registration{/title} +{#moreScripts} + +{/moreScripts} + +{#form uri:Login.complete(newUser.confirmationCode)} + +
+ Complete registration for {newUser.email} + {#formElement name="userName" label="User Name"} + {#input name="userName" value=newUser.userName id="username"/} + {/formElement} + {#if !newUser.authId} + {#formElement name="password" label="Password" class="input-group"} + {#input name="password" type="password" id="password"/} + + {/formElement} + {#formElement name="password2" label="Password Confirmation"} + {#input name="password2" type="password" id="password2"/} + {/formElement} + {/if} + {#formElement name="firstName" label="First Name"} + {#input name="firstName" value=newUser.firstName id="firstname"/} + {/formElement} + {#formElement name="lastName" label="Last Name"} + {#input name="lastName" value=newUser.lastName id="lastname"/} + {/formElement} + + {#input name="webAuthnId" type="hidden" id="webAuthnId"/} + {#input name="webAuthnRawId" type="hidden" id="webAuthnRawId"/} + {#input name="webAuthnResponseClientDataJSON" type="hidden" id="webAuthnResponseClientDataJSON"/} + {#input name="webAuthnResponseAttestationObject" type="hidden" id="webAuthnResponseAttestationObject"/} + {#input name="webAuthnType" type="hidden" id="webAuthnType"/} + + +
+ +{/form} + + + +{/include} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/login.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/login.html new file mode 100644 index 000000000..ae646f4b8 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/login.html @@ -0,0 +1,90 @@ +{#include main.html } +{#title}Login{/title} +{#moreScripts} + +{/moreScripts} + +
+
+
+ Login/Register + +
+
+
+ {#form uri:Login.manualLogin() id="login"} +
+ Login + {#formElement name="userName" label="User Name" class="input-group"} + {#input name="userName" id="username"/} + + {/formElement} + {#formElement name="password" label="Password" class="input-group"} + {#input name="password" type="password" id="password"/} + + {/formElement} + + {#input name="webAuthnId" type="hidden" id="webAuthnId"/} + {#input name="webAuthnRawId" type="hidden" id="webAuthnRawId"/} + {#input name="webAuthnResponseClientDataJSON" type="hidden" id="webAuthnResponseClientDataJSON"/} + {#input name="webAuthnResponseAuthenticatorData" type="hidden" id="webAuthnResponseAuthenticatorData"/} + {#input name="webAuthnResponseSignature" type="hidden" id="webAuthnResponseSignature"/} + {#input name="webAuthnResponseUserHandle" type="hidden" id="webAuthnResponseUserHandle"/} + {#input name="webAuthnType" type="hidden" id="webAuthnType"/} +
+ {/form} +
+
+ {#form uri:Login.register()} +
+ Register + {#formElement name="email" label="Email"} + {#input name="email"/} + {/formElement} + +
+ {/form} +
+
+ + + +{/include} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/logoutFirst.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/logoutFirst.html new file mode 100644 index 000000000..faeeabbd9 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/logoutFirst.html @@ -0,0 +1,11 @@ +{#include main.html } +{#title}Logout before completing registration{/title} + +
+
+
+ You must log out first in order to complete registration. +
+
+
+{/include} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/register.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/register.html new file mode 100644 index 000000000..f6ae47c60 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/register.html @@ -0,0 +1,14 @@ +{#include main.html } +{#title}Check your email for confirmation{/title} + +
+
+
+

+ An email has been sent to {email} to verify your email address. + Please follow the instructions in this email in order to complete your registration. +

+
+
+
+{/include} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/welcome.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/welcome.html new file mode 100644 index 000000000..14c0433ce --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Login/welcome.html @@ -0,0 +1,16 @@ +{#include main.html } +{#title}Home{/title} + +
+
+
+

+ Welcome, {inject:user.userName} +

+

+ Your registration is complete. You've been logged in. +

+
+
+
+{/include} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Todos/index.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Todos/index.html new file mode 100644 index 000000000..9fc15ff7c --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/Todos/index.html @@ -0,0 +1,49 @@ +{#include main.html } +{#title}Todos{/title} + + + + + + + + + + + {#for todo in todos} + + + + + + {/for} + + + + + + +
#TaskAction
{todo.id} + {#if todo.done} + {todo.task} (done {todo.doneDate.since}) + {#else} + {todo.task} + {/if} + + {#form uri:Todos.done(todo.id) klass="inline"} + {#if todo.done} + + {#else} + + {/if} + {/form} + {#form uri:Todos.delete(todo.id) klass="inline"} + + {/form} +
New + {#form uri:Todos.add()} + {#input name="task" placeholder="Type task and press ENTER"/} + {/form} +
+ +{/include} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/email.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/email.html new file mode 100644 index 000000000..f62e3c90f --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/email.html @@ -0,0 +1,13 @@ + + + + + + + + {#insert /} +

+ This is an automated email, you should not reply to it: your mail will be ignored. +

+ + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/email.txt b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/email.txt new file mode 100644 index 000000000..3930319fc --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/email.txt @@ -0,0 +1,3 @@ +{#insert /} + +This is an automated email, you should not reply to it: your mail will be ignored. diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/main.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/main.html new file mode 100644 index 000000000..2534b7ee9 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/main.html @@ -0,0 +1,64 @@ + + + + {#insert title /} + + + + + + {#insert moreStyles /} + + + + {#insert moreScripts /} + + + +
+ {#if flash:message} +
+ {flash:message} +
+ {/if} + {#insert /} +
+
+
+
+ © FroMage 2013-2021 +
+
+ + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/formElement.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/formElement.html new file mode 100644 index 000000000..58ed6e8c9 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/formElement.html @@ -0,0 +1,7 @@ + +
+ {nested-content} +{#ifError name} + ​{#error name/}​ +{/ifError} +
diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/input.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/input.html new file mode 100644 index 000000000..054fdf545 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/input.html @@ -0,0 +1,7 @@ + diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/user.html b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/user.html new file mode 100644 index 000000000..1ed2e22f3 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/main/resources/templates/tags/user.html @@ -0,0 +1,8 @@ +{#if it??} + + +{#if img??} +{#gravatar it.email size=size.or(20) default='mm' /} +{/if} +{it.userName} +{/if} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/test/java/fr/epardaud/NativeReactiveGreetingResourceIT.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/test/java/fr/epardaud/NativeReactiveGreetingResourceIT.java new file mode 100644 index 000000000..ee73929d9 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/test/java/fr/epardaud/NativeReactiveGreetingResourceIT.java @@ -0,0 +1,9 @@ +package fr.epardaud; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class NativeReactiveGreetingResourceIT extends TodoResourceTest { + + // Execute the same tests but in native mode. +} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/test/java/fr/epardaud/TodoResourceTest.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/test/java/fr/epardaud/TodoResourceTest.java new file mode 100644 index 000000000..599eec4e2 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/quarkus-renarde-todo/src/test/java/fr/epardaud/TodoResourceTest.java @@ -0,0 +1,519 @@ +package fr.epardaud; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import javax.inject.Inject; + +import org.apache.http.client.CookieStore; +import org.apache.http.cookie.Cookie; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.quarkiverse.renarde.oidc.test.MockAppleOidc; +import io.quarkiverse.renarde.oidc.test.MockFacebookOidc; +import io.quarkiverse.renarde.oidc.test.MockGithubOidc; +import io.quarkiverse.renarde.oidc.test.MockGoogleOidc; +import io.quarkiverse.renarde.oidc.test.MockMicrosoftOidc; +import io.quarkiverse.renarde.oidc.test.MockTwitterOidc; +import io.quarkiverse.renarde.oidc.test.RenardeCookieFilter; +import io.quarkiverse.renarde.util.Flash; +import io.quarkiverse.renarde.util.JavaExtensions; +import io.quarkus.mailer.Mail; +import io.quarkus.mailer.MockMailbox; +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.webauthn.WebAuthnEndpointHelper; +import io.quarkus.test.security.webauthn.WebAuthnHardware; +import io.restassured.filter.Filter; +import io.restassured.path.json.JsonPath; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import io.restassured.response.ValidatableResponse; +import io.restassured.specification.RequestSpecification; +import io.smallrye.jwt.build.Jwt; +import io.vertx.core.json.JsonObject; + +@MockFacebookOidc +@MockGoogleOidc +@MockAppleOidc +@MockMicrosoftOidc +@MockTwitterOidc +@MockGithubOidc +@QuarkusTest +public class TodoResourceTest { + + @TestHTTPResource + String url; + + @Inject + MockMailbox mailbox; + + @BeforeEach + void init() { + mailbox.clear(); + } + + @Test + public void testMainPage() { + given() + .when().get("/") + .then() + .statusCode(200) + .body("html.head.title", is("Welcome to Todos")); + } + + @Test + public void testProtectedPage() { + // cannot go to Todo page + given() + .when() + .redirects().follow(false) + .get("/Todos/index") + .then() + .statusCode(302); + } + + @Test + public void testProtectedPageWithInvalidJwt() throws NoSuchAlgorithmException { + // canary: valid + String token = Jwt.issuer("https://example.com/issuer") + .upn("fromage") + .issuedAt(Instant.now()) + .expiresIn(Duration.ofDays(10)) + .innerSign().encrypt(); + // valid + given() + .when() + .cookie("QuarkusUser", token) + .log().ifValidationFails() + .redirects().follow(false) + .get("/") + .then() + .log().ifValidationFails() + .statusCode(200); + // expired + token = Jwt.issuer("https://example.com/issuer") + .upn("fromage") + .issuedAt(Instant.now().minus(20, ChronoUnit.DAYS)) + .expiresIn(Duration.ofDays(10)) + .innerSign().encrypt(); + assertRedirectWithMessage(token, "Login expired, you've been logged out"); + // invalid issuer + token = Jwt.issuer("https://example.com/other-issuer") + .upn("fromage") + .issuedAt(Instant.now()) + .expiresIn(Duration.ofDays(10)) + .innerSign().encrypt(); + assertRedirectWithMessage(token, "Invalid session (bad JWT), you've been logged out"); + // invalid signature + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + token = Jwt.issuer("https://example.com/issuer") + .upn("fromage") + .issuedAt(Instant.now()) + .expiresIn(Duration.ofDays(10)) + .innerSign(kp.getPrivate()).encrypt(kp.getPublic()); + assertRedirectWithMessage(token, "Invalid session (bad signature), you've been logged out"); + // invalid user + token = Jwt.issuer("https://example.com/issuer") + .upn("cheesy") + .issuedAt(Instant.now()) + .expiresIn(Duration.ofDays(10)) + .innerSign().encrypt(); + assertRedirectWithMessage(token, "Invalid user: cheesy"); + } + + private void assertRedirectWithMessage(String token, String message) { + // redirect with message + String flash = given() + .when() + .cookie("QuarkusUser", token) + .log().ifValidationFails() + .redirects().follow(false) + .get("/") + .then() + .log().ifValidationFails() + .statusCode(303) + // logout + .cookie("QuarkusUser", "") + .extract().cookie(Flash.FLASH_COOKIE_NAME); + Map data = Flash.decodeCookieValue(flash); + Assertions.assertTrue(data.containsKey("message")); + Assertions.assertEquals(message, data.get("message")); + } + + @Test + public void testManualRegistration() { + String confirmationCode = register("manual"); + + RenardeCookieFilter cookieFilter = new RenardeCookieFilter(); + completeRegistration(confirmationCode, cookieFilter, "manual", "shield-lock", request -> { + request + .formParam("userName", "manual") + .formParam("password", "1q2w3e4r") + .formParam("password2", "1q2w3e4r") + .formParam("firstName", "Stef") + .formParam("lastName", "Epardaud"); + }); + } + + @Test + public void testWebAuthnRegistration() { + String confirmationCode = register("webauthn"); + + RenardeCookieFilter cookieFilter = new RenardeCookieFilter(); + WebAuthnHardware token = new WebAuthnHardware(); + String challenge = WebAuthnEndpointHelper.invokeRegistration("webauthn", cookieFilter); + + JsonObject registrationJson = token.makeRegistrationJson(challenge); + + completeRegistration(confirmationCode, cookieFilter, "webauthn", "fingerprint", request -> { + WebAuthnEndpointHelper.addWebAuthnRegistrationFormParameters(request, registrationJson); + request + .formParam("userName", "webauthn") + .formParam("firstName", "Stef") + .formParam("lastName", "Epardaud"); + }); + + // now try logging in + challenge = WebAuthnEndpointHelper.invokeLogin("webauthn", cookieFilter); + + JsonObject loginJson = token.makeLoginJson(challenge); + testManualLogin(cookieFilter, "webauthn", "fingerprint", request -> { + WebAuthnEndpointHelper.addWebAuthnLoginFormParameters(request, loginJson); + request + .formParam("userName", "webauthn"); + }); + } + + private void completeRegistration(String confirmationCode, Filter cookieFilter, String userName, String icon, Consumer completeCustomiser) { + // confirm form action + RequestSpecification completeRequest = given() + .when() + .queryParam("confirmationCode", confirmationCode); + + completeCustomiser.accept(completeRequest); + + completeRequest + .log().ifValidationFails() + .filter(cookieFilter) + .redirects().follow(false) + .post("/Login/complete") + .then() + .log().ifValidationFails() + .statusCode(303) + .cookie("QuarkusUser") + .header("Location", url+"Login/welcome"); + + testLoggedIn(userName, icon, cookieFilter); + } + + private String register(String userName) { + // register email + given() + .when() + .formParam("email", userName+"@example.com") + .post("/Login/register") + .then() + .statusCode(200) + .body("html.head.title", is("Check your email for confirmation")) + .body(containsString("An email has been sent to "+userName+"@example.com")); + + // get the confirmation email + List mails = mailbox.getMessagesSentTo(userName+"@example.com"); + Assertions.assertEquals(1, mails.size()); + Mail mail = mails.get(0); + Assertions.assertNotNull(mail.getText()); + Assertions.assertNotNull(mail.getHtml()); + String linkStart = "If you want to register, complete your registration by going to the following address:\n" + + "\n" + url; + int absoluteUriIndex = mail.getText().indexOf(linkStart) + linkStart.length() - 1; + Assertions.assertTrue(absoluteUriIndex > -1, "Failed to find confirmation URI in email: "+mail.getText()); + String confirmationPath = mail.getText().substring(absoluteUriIndex); + Assertions.assertTrue(confirmationPath.startsWith("/Login/confirm?confirmationCode="), "Failed to parse confirmation path: "+confirmationPath); + Assertions.assertTrue(confirmationPath.indexOf('\n') > -1); + String confirmationCode = confirmationPath.substring("/Login/confirm?confirmationCode=".length(), confirmationPath.indexOf('\n')); + Assertions.assertFalse(confirmationCode.isEmpty()); + + // confirm page + given() + .when() + .queryParam("confirmationCode", confirmationCode) + .get("/Login/confirm") + .then() + .statusCode(200) + .body("html.head.title", is("Complete registration")); + + return confirmationCode; + } + + private void testLoggedIn(String userName, String icon, Filter cookieFilter) { + // welcome page + given() + .when() + .filter(cookieFilter) + .get("/Login/welcome") + .then() + .statusCode(200) + .body(containsString("Home")) + // alert + .body(containsString("Welcome, "+userName)) + // user gravatar in menu + .body(containsString("\n" + + "\n" + + userName+"")) + // Todo link + .body(containsString("Todos")) + // Logout link + .body(containsString("Logout")); + + // can go to Todo page + given() + .when() + .filter(cookieFilter) + .get("/Todos/index") + .then() + .statusCode(200); + + // now logout + given() + .when() + .filter(cookieFilter) + .redirects().follow(false) + .get("/_renarde/security/logout") + .then() + .statusCode(303) + // go home + .header("Location", url) + // clear cookie + .cookie("QuarkusUser", ""); + + } + + @Test + public void testManualLogin() { + RenardeCookieFilter cookieFilter = new RenardeCookieFilter(); + testManualLogin(cookieFilter, "fromage", "shield-lock", request -> { + request + .formParam("userName", "fromage") + .formParam("password", "1q2w3e4r"); + }); + } + + private void testManualLogin(Filter cookieFilter, String userName, String icon, Consumer requestCustomiser) { + // login form action + RequestSpecification request = given() + .when(); + requestCustomiser.accept(request); + request + .filter(cookieFilter) + .redirects().follow(false) + .post("/Login/manualLogin") + .then() + .statusCode(303) + .cookie("QuarkusUser") + .header("Location", url); + + testLoggedIn(userName, icon, cookieFilter); + } + + private void oidcTest(String provider, String email, String firstName, String lastName, String userName) { + RenardeCookieFilter cookieFilter = new RenardeCookieFilter(); + ValidatableResponse response = follow("/_renarde/security/login-"+provider, cookieFilter); + response.statusCode(200) + .body(containsString("Complete registration for "+email)) + // lastname and username + .body(containsString("value=\""+lastName+"\"/>")) + // firstname + .body(containsString("value=\""+firstName+"\"/>")) + ; + + Assertions.assertNotNull(findCookie(cookieFilter.getCookieStore(), "q_session_"+provider)); + + String body = response.extract().body().asString(); + String clue = "
-1); + int codeIndex = clueIndex + clue.length(); + String confirmationCode = body.substring(codeIndex, body.indexOf('"', codeIndex)); + + finishConfirmation(cookieFilter, confirmationCode, + firstName, lastName, userName, email, "q_session_"+provider); + + } + + @Test + public void githubLoginTest() { + oidcTest("github", "octocat@github.com", "monalisa", "octocat", "octocat"); + } + + @Test + public void twitterLoginTest() { + oidcTest("twitter", "twitter@example.com", "Foo", "Bar", "TwitterUser"); + } + + @Test + public void googleLoginTest() { + oidcTest("google", "google@example.com", "Foo", "Bar", "GoogleUser"); + } + + @Test + public void microsoftLoginTest() { + oidcTest("microsoft", "microsoft@example.com", "Foo", "Bar", "MicrosoftUser"); + } + + @Test + public void facebookLoginTest() { + oidcTest("facebook", "facebook@example.com", "Foo", "Bar", "FacebookUser"); + } + + @Test + public void appleLoginTest() { + RenardeCookieFilter cookieFilter = new RenardeCookieFilter(); + ValidatableResponse response = follow("/_renarde/security/login-apple", cookieFilter); + JsonPath json = response.statusCode(200) + .extract().body().jsonPath(); + String code = json.get("code"); + String state = json.get("state"); + + String location = given() + .when() + .filter(cookieFilter) + .formParam("state", state) + .formParam("code", code) + // can't follow redirects due to cookies + .redirects().follow(false) + // must be precise and not contain an encoding: probably needs fixing in the OIDC side + .contentType("application/x-www-form-urlencoded") + .log().ifValidationFails() + .post("/_renarde/security/oidc-success") + .then() + .log().ifValidationFails() + .statusCode(302) + .extract().header("Location"); + // now move on to the GET, but make sure we go over http + ValidatableResponse completeResponse = follow(location.replace("https://", "http://"), cookieFilter) + .body(containsString("Complete registration for apple@example.com")) + // no name, username from apple + ; + + Assertions.assertNotNull(findCookie(cookieFilter.getCookieStore(), "q_session_apple")); + + String body = completeResponse.extract().body().asString(); + String clue = " -1); + int codeIndex = clueIndex + clue.length(); + String confirmationCode = body.substring(codeIndex, body.indexOf('"', codeIndex)); + + finishConfirmation(cookieFilter, confirmationCode, + "Foo", "Bar", "AppleUser", "apple@example.com", "q_session_apple"); + } + + private void finishConfirmation(RenardeCookieFilter cookieFilter, String confirmationCode, + String firstName, String lastName, String userName, String email, + String cookieName) { + // confirm form action + given() + .when() + .queryParam("confirmationCode", confirmationCode) + .formParam("userName", userName) + .formParam("firstName", firstName) + .formParam("lastName", lastName) + .filter(cookieFilter) + .redirects().follow(false) + .post("/Login/complete") + .then() + .statusCode(303) + .header("Location", url+"Login/welcome"); + + // welcome page + given() + .when() + .filter(cookieFilter) + .get("/Login/welcome") + .then() + .statusCode(200) + .body(containsString("Home")) + // alert + .body(containsString("Welcome, "+userName)) + // user gravatar in menu + .body(containsString("\n" + + "\n" + + userName+"")) + // Todo link + .body(containsString("Todos")) + // Logout link + .body(containsString("Logout")); + + // can go to Todo page + given() + .when() + .filter(cookieFilter) + .get("/Todos/index") + .then() + .statusCode(200); + + // now logout + given() + .when() + .filter(cookieFilter) + .redirects().follow(false) + .get("/_renarde/security/logout") + .then() + .statusCode(303) + // go home + .header("Location", url) + // clear cookie + .cookie(cookieName, ""); + } + + private Object findCookie(CookieStore cookieStore, String name) { + for (Cookie cookie : cookieStore.getCookies()) { + if(cookie.getName().equals(name)) { + return cookie; + } + } + return null; + } + + private ValidatableResponse follow(String uri, RenardeCookieFilter cookieFilter) { + do { + // make sure we turn any https into http, because some providers force https + if(uri.startsWith("https://")) { + uri = "http" + uri.substring(5); + } + ValidatableResponse response = given() + .when() + .filter(cookieFilter) + // mandatory for Location redirects + .urlEncodingEnabled(false) + .redirects().follow(false) + .log().ifValidationFails() + .get(uri) + .then() + .log().ifValidationFails(); + ExtractableResponse extract = response.extract(); + if(extract.statusCode() == 302 + || extract.statusCode() == 303) { + uri = extract.header("Location"); + } else { + return response; + } + } while (true); + } +} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/QuteAssert.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/QuteAssert.java new file mode 100644 index 000000000..3e0911dbc --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/QuteAssert.java @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.junit.Assert; + +import com.redhat.qute.commons.datamodel.DataModelParameter; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverInfo; + +/** + * Qute assert. + * + * @author Angelo ZERR + * + */ +public class QuteAssert { + + public static void assertValueResolver(String namespace, String signature, String sourceType, + List resolvers) { + assertValueResolver(namespace, signature, sourceType, null, resolvers); + } + + public static void assertValueResolver(String namespace, String signature, String sourceType, String named, + List resolvers) { + assertValueResolver(namespace, signature, sourceType, named, false, resolvers); + } + + public static void assertValueResolver(String namespace, String signature, String sourceType, String named, + boolean globalVariable, List resolvers) { + Optional result = resolvers.stream() + .filter(r -> signature.equals(r.getSignature()) && Objects.equals(namespace, r.getNamespace())) + .findFirst(); + Assert.assertFalse("Find '" + signature + "' value resolver.", result.isEmpty()); + ValueResolverInfo resolver = result.get(); + Assert.assertEquals(namespace, resolver.getNamespace()); + Assert.assertEquals(signature, resolver.getSignature()); + Assert.assertEquals(sourceType, resolver.getSourceType()); + Assert.assertEquals(globalVariable, resolver.isGlobalVariable()); + } + + public static void assertNotValueResolver(String namespace, String signature, String sourceType, String named, + List resolvers) { + assertNotValueResolver(namespace, signature, sourceType, named, false, resolvers); + } + + public static void assertNotValueResolver(String namespace, String signature, String sourceType, String named, + boolean globalVariable, List resolvers) { + Optional result = resolvers.stream().filter(r -> signature.equals(r.getSignature())) + .findFirst(); + Assert.assertTrue("Find '" + signature + "' value resolver.", result.isEmpty()); + } + + public static void assertParameter(String key, String sourceType, boolean dataMethodInvocation, + List parameters, int index) { + DataModelParameter parameter = parameters.get(index); + Assert.assertEquals(key, parameter.getKey()); + Assert.assertEquals(sourceType, parameter.getSourceType()); + Assert.assertEquals(dataMethodInvocation, parameter.isDataMethodInvocation()); + } +} diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/QuteProjectTest.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/QuteProjectTest.java index 32ac4dcee..57fcc2779 100644 --- a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/QuteProjectTest.java +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/QuteProjectTest.java @@ -61,6 +61,8 @@ public static class QuteMavenProjectName { public static String qute_quickstart = "qute-quickstart"; public static String qute_java17 = "qute-java17"; + + public static String quarkus_renarde_todo = "quarkus-renarde-todo"; } @BeforeClass @@ -118,22 +120,9 @@ public void run(IProgressMonitor monitor) throws CoreException { }; IProgressMonitor monitor = new NullProgressMonitor(); JavaCore.run(runnable, null, monitor); - waitForBackgroundJobs(monitor); + waitForBackgroundJobs(monitor); + JobHelpers.waitUntilIndexesReady(); } - // Collect Quarkus properties from the "hibernate-orm-resteasy" project. It - // should collect Quarkus properties from given JAR: - - // 1) quarkus-hibernate-orm.jar which is declared in the dependencies of the - // pom.xml - // - // io.quarkus - // quarkus-hibernate-orm - // - - // 2) quarkus-hibernate-orm-deployment.jar which is declared in - // META-INF/quarkus-extension.properties of quarkus-hibernate-orm.jar as - // property: - // deployment-artifact=io.quarkus\:quarkus-hibernate-orm-deployment\:0.21.1 return JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(description.getName()); } diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/internal/JobHelpers.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/internal/JobHelpers.java index 0f3eb955c..6fa33e44f 100644 --- a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/internal/JobHelpers.java +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/internal/JobHelpers.java @@ -27,20 +27,27 @@ import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.jobs.IJobManager; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeNameRequestor; +import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.handlers.BaseInitHandler; import org.eclipse.m2e.core.internal.embedder.MavenExecutionContext; import org.eclipse.m2e.core.internal.jobs.IBackgroundProcessingQueue; /** - * Copied from m2e's org.eclipse.m2e.tests.common/src/org/eclipse/m2e/tests/common/JobHelpers.java + * Copied from m2e's + * org.eclipse.m2e.tests.common/src/org/eclipse/m2e/tests/common/JobHelpers.java * */ @SuppressWarnings("restriction") public final class JobHelpers { private JobHelpers() { - //no instantiation + // no instantiation } private static final int POLLING_DELAY = 10; @@ -49,7 +56,7 @@ private JobHelpers() { public static void waitForJobsToComplete() { try { waitForJobsToComplete(new NullProgressMonitor()); - } catch(Exception ex) { + } catch (Exception ex) { throw new IllegalStateException(ex); } } @@ -60,23 +67,24 @@ public static void waitForJobsToComplete(IProgressMonitor monitor) throws Interr /* * First, make sure refresh job gets all resource change events * - * Resource change events are delivered after WorkspaceJob#runInWorkspace returns - * and during IWorkspace#run. Each change notification is delivered by + * Resource change events are delivered after WorkspaceJob#runInWorkspace + * returns and during IWorkspace#run. Each change notification is delivered by * only one thread/job, so we make sure no other workspaceJob is running then * call IWorkspace#run from this thread. * - * Unfortunately, this does not catch other jobs and threads that call IWorkspace#run - * so we have to hard-code workarounds + * Unfortunately, this does not catch other jobs and threads that call + * IWorkspace#run so we have to hard-code workarounds * - * See http://www.eclipse.org/articles/Article-Resource-deltas/resource-deltas.html + * See + * http://www.eclipse.org/articles/Article-Resource-deltas/resource-deltas.html */ IWorkspace workspace = ResourcesPlugin.getWorkspace(); IJobManager jobManager = Job.getJobManager(); jobManager.suspend(); try { Job[] jobs = jobManager.find(null); - for(int i = 0; i < jobs.length; i++ ) { - if(jobs[i] instanceof WorkspaceJob || jobs[i].getClass().getName().endsWith("JREUpdateJob")) { + for (int i = 0; i < jobs.length; i++) { + if (jobs[i] instanceof WorkspaceJob || jobs[i].getClass().getName().endsWith("JREUpdateJob")) { jobs[i].join(); } } @@ -88,15 +96,16 @@ public void run(IProgressMonitor monitor) { // Now we flush all background processing queues boolean processed = flushProcessingQueues(jobManager, monitor); - for(int i = 0; i < 10 && processed; i++ ) { + for (int i = 0; i < 10 && processed; i++) { processed = flushProcessingQueues(jobManager, monitor); try { Thread.sleep(10); - } catch(InterruptedException e) { + } catch (InterruptedException e) { } } if (processed) { - JavaLanguageServerPlugin.logInfo("Could not flush background processing queues: " + getProcessingQueues(jobManager)); + JavaLanguageServerPlugin + .logInfo("Could not flush background processing queues: " + getProcessingQueues(jobManager)); } } finally { jobManager.resume(); @@ -108,9 +117,9 @@ public void run(IProgressMonitor monitor) { private static boolean flushProcessingQueues(IJobManager jobManager, IProgressMonitor monitor) throws InterruptedException, CoreException { boolean processed = false; - for(IBackgroundProcessingQueue queue : getProcessingQueues(jobManager)) { + for (IBackgroundProcessingQueue queue : getProcessingQueues(jobManager)) { queue.join(); - if(!queue.isEmpty()) { + if (!queue.isEmpty()) { Deque context = MavenExecutionContext.suspend(); try { IStatus status = queue.run(monitor); @@ -122,7 +131,7 @@ private static boolean flushProcessingQueues(IJobManager jobManager, IProgressMo MavenExecutionContext.resume(context); } } - if(queue.isEmpty()) { + if (queue.isEmpty()) { queue.cancel(); } } @@ -131,8 +140,8 @@ private static boolean flushProcessingQueues(IJobManager jobManager, IProgressMo private static List getProcessingQueues(IJobManager jobManager) { ArrayList queues = new ArrayList<>(); - for(Job job : jobManager.find(null)) { - if(job instanceof IBackgroundProcessingQueue) { + for (Job job : jobManager.find(null)) { + if (job instanceof IBackgroundProcessingQueue) { queues.add((IBackgroundProcessingQueue) job); } } @@ -144,8 +153,8 @@ public static void waitForWorkspaceJobsToComplete(IProgressMonitor monitor) thro jobManager.suspend(); try { Job[] jobs = jobManager.find(null); - for(int i = 0; i < jobs.length; i++ ) { - if(jobs[i] instanceof WorkspaceJob || jobs[i].getClass().getName().endsWith("JREUpdateJob")) { + for (int i = 0; i < jobs.length; i++) { + if (jobs[i] instanceof WorkspaceJob || jobs[i].getClass().getName().endsWith("JREUpdateJob")) { jobs[i].join(); } } @@ -176,9 +185,9 @@ public static void waitForDownloadSourcesJobs(int maxTimeMillis) { public static void waitForJobs(IJobMatcher matcher, int maxWaitMillis) { final long limit = System.currentTimeMillis() + maxWaitMillis; - while(true) { + while (true) { Job job = getJob(matcher); - if(job == null) { + if (job == null) { return; } boolean timeout = System.currentTimeMillis() > limit; @@ -189,7 +198,7 @@ public static void waitForJobs(IJobMatcher matcher, int maxWaitMillis) { job.wakeUp(); try { Thread.sleep(POLLING_DELAY); - } catch(InterruptedException e) { + } catch (InterruptedException e) { // ignore and keep waiting } } @@ -197,8 +206,8 @@ public static void waitForJobs(IJobMatcher matcher, int maxWaitMillis) { private static Job getJob(IJobMatcher matcher) { Job[] jobs = Job.getJobManager().find(null); - for(Job job : jobs) { - if(matcher.matches(job)) { + for (Job job : jobs) { + if (matcher.matches(job)) { return job; } } @@ -262,7 +271,8 @@ static class ProjectRegistryRefreshJobMatcher implements IJobMatcher { @Override public boolean matches(Job job) { - return job.getClass().getName().matches("org.eclipse.m2e.core.internal.project.registry.ProjectRegistryRefreshJob"); + return job.getClass().getName() + .matches("org.eclipse.m2e.core.internal.project.registry.ProjectRegistryRefreshJob"); } } @@ -284,7 +294,8 @@ static class LoadingGradleVersionJobMatcher implements IJobMatcher { @Override public boolean matches(Job job) { - return job.getClass().getName().matches("org.eclipse.buildship.core.internal.util.gradle.PublishedGradleVersionsWrapper.LoadVersionsJob"); + return job.getClass().getName().matches( + "org.eclipse.buildship.core.internal.util.gradle.PublishedGradleVersionsWrapper.LoadVersionsJob"); } } @@ -300,4 +311,24 @@ public boolean matches(Job job) { } + // copied from + // ./org.eclipse.jdt.core.tests.performance/src/org/eclipse/jdt/core/tests/performance/FullSourceWorkspaceTests.java + public static void waitUntilIndexesReady() { + // dummy query for waiting until the indexes are ready + SearchEngine engine = new SearchEngine(); + IJavaSearchScope scope = SearchEngine.createWorkspaceScope(); + JavaModelManager.getIndexManager().waitForIndex(true, null); + try { + engine.searchAllTypeNames(null, SearchPattern.R_EXACT_MATCH, "!@$#!@".toCharArray(), + SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE, IJavaSearchConstants.CLASS, scope, + new TypeNameRequestor() { + @Override + public void acceptType(int modifiers, char[] packageName, char[] simpleTypeName, + char[][] enclosingTypeNames, String path) { + } + }, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, null); + } catch (CoreException e) { + JavaLanguageServerPlugin.logException(e.getMessage(), e); + } + } } \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/TemplateGetDataModelProjectTest.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/TemplateGetDataModelProjectTest.java index 199b0a07d..0f394ebe1 100644 --- a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/TemplateGetDataModelProjectTest.java +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/TemplateGetDataModelProjectTest.java @@ -11,11 +11,13 @@ *******************************************************************************/ package com.redhat.qute.jdt.template; +import static com.redhat.qute.jdt.QuteAssert.assertNotValueResolver; +import static com.redhat.qute.jdt.QuteAssert.assertParameter; +import static com.redhat.qute.jdt.QuteAssert.assertValueResolver; import static com.redhat.qute.jdt.QuteProjectTest.getJDTUtils; import static com.redhat.qute.jdt.QuteProjectTest.loadMavenProject; import java.util.List; -import java.util.Optional; import org.eclipse.core.runtime.NullProgressMonitor; import org.junit.Assert; @@ -360,46 +362,4 @@ private static void testValueResolversFromTemplateGlobal(List // } } - private static void assertValueResolver(String namespace, String signature, String sourceType, - List resolvers) { - assertValueResolver(namespace, signature, sourceType, null, resolvers); - } - - private static void assertValueResolver(String namespace, String signature, String sourceType, String named, - List resolvers) { - assertValueResolver(namespace, signature, sourceType, named, false, resolvers); - } - - private static void assertValueResolver(String namespace, String signature, String sourceType, String named, - boolean globalVariable, List resolvers) { - Optional result = resolvers.stream().filter(r -> signature.equals(r.getSignature())) - .findFirst(); - Assert.assertFalse("Find '" + signature + "' value resolver.", result.isEmpty()); - ValueResolverInfo resolver = result.get(); - Assert.assertEquals(namespace, resolver.getNamespace()); - Assert.assertEquals(signature, resolver.getSignature()); - Assert.assertEquals(sourceType, resolver.getSourceType()); - Assert.assertEquals(globalVariable, resolver.isGlobalVariable()); - } - - private static void assertNotValueResolver(String namespace, String signature, String sourceType, String named, - List resolvers) { - assertNotValueResolver(namespace, signature, sourceType, named, false, resolvers); - } - - private static void assertNotValueResolver(String namespace, String signature, String sourceType, String named, - boolean globalVariable, List resolvers) { - Optional result = resolvers.stream().filter(r -> signature.equals(r.getSignature())) - .findFirst(); - Assert.assertTrue("Find '" + signature + "' value resolver.", result.isEmpty()); - } - - private static void assertParameter(String key, String sourceType, boolean dataMethodInvocation, - List parameters, int index) { - DataModelParameter parameter = parameters.get(index); - Assert.assertEquals(key, parameter.getKey()); - Assert.assertEquals(sourceType, parameter.getSourceType()); - Assert.assertEquals(dataMethodInvocation, parameter.isDataMethodInvocation()); - } - } diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/TemplateGetResolvedJavaTypeTest.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/TemplateGetResolvedJavaTypeTest.java index 26a92f052..60b6ebd42 100644 --- a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/TemplateGetResolvedJavaTypeTest.java +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/TemplateGetResolvedJavaTypeTest.java @@ -28,6 +28,10 @@ import com.redhat.qute.commons.JavaTypeKind; import com.redhat.qute.commons.QuteResolvedJavaTypeParams; import com.redhat.qute.commons.ResolvedJavaTypeInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; +import com.redhat.qute.commons.jaxrs.JaxRsMethodKind; +import com.redhat.qute.commons.jaxrs.JaxRsParamKind; +import com.redhat.qute.commons.jaxrs.RestParam; import com.redhat.qute.jdt.QuteProjectTest.QuteMavenProjectName; import com.redhat.qute.jdt.QuteSupportForTemplate; @@ -248,7 +252,7 @@ public void mapEntry() throws Exception { new NullProgressMonitor()); Assert.assertNull(result); } - + @Test public void someInterface() throws Exception { loadMavenProject(QuteMavenProjectName.qute_quickstart); @@ -533,6 +537,125 @@ public void generic() throws CoreException, Exception { Assert.assertTrue(extendedTypes.isEmpty()); } + @Test + public void renarde() throws CoreException, Exception { + loadMavenProject(QuteMavenProjectName.quarkus_renarde_todo); + + // class Login extends ControllerWithUser + QuteResolvedJavaTypeParams params = new QuteResolvedJavaTypeParams("rest.Login", ValueResolverKind.Renarde, + QuteMavenProjectName.quarkus_renarde_todo); + ResolvedJavaTypeInfo result = QuteSupportForTemplate.getInstance().getResolvedJavaType(params, getJDTUtils(), + new NullProgressMonitor()); + Assert.assertNotNull(result); + + List extendedTypes = result.getExtendedTypes(); + Assert.assertNotNull(extendedTypes); + Assert.assertEquals(1, extendedTypes.size()); + assertExtendedTypes("rest.Login", "io.quarkiverse.renarde.oidc.ControllerWithUser", extendedTypes); + + Assert.assertNotNull(result.getMethods()); + Assert.assertEquals(7, result.getMethods().size()); + + // login + JavaMethodInfo loginMethod = result.getMethods().get(0); + Assert.assertEquals("login() : io.quarkus.qute.TemplateInstance", loginMethod.getSignature()); + Assert.assertEquals(JaxRsMethodKind.GET, loginMethod.getJaxRsMethodKind()); + + // welcome + JavaMethodInfo welcomeMethod = result.getMethods().get(1); + Assert.assertEquals("welcome() : io.quarkus.qute.TemplateInstance", welcomeMethod.getSignature()); + Assert.assertEquals(JaxRsMethodKind.GET, welcomeMethod.getJaxRsMethodKind()); + + // manualLogin + JavaMethodInfo manualLoginMethod = result.getMethods().get(2); + Assert.assertEquals( + "manualLogin(userName : java.lang.String, password : java.lang.String, webAuthnResponse : io.quarkus.security.webauthn.WebAuthnLoginResponse, ctx : io.vertx.ext.web.RoutingContext) : javax.ws.rs.core.Response", + manualLoginMethod.getSignature()); + Assert.assertEquals(JaxRsMethodKind.POST, manualLoginMethod.getJaxRsMethodKind()); + Assert.assertNotNull(manualLoginMethod.getRestParameters()); + Assert.assertEquals(2, manualLoginMethod.getRestParameters().size()); + + RestParam password = manualLoginMethod.getRestParameter("password"); + Assert.assertNotNull(password); + Assert.assertEquals("password", password.getName()); + Assert.assertEquals(JaxRsParamKind.FORM, password.getParameterKind()); + + RestParam userName = manualLoginMethod.getRestParameter("userName"); + Assert.assertNotNull(userName); + Assert.assertEquals("userName", userName.getName()); + Assert.assertEquals(JaxRsParamKind.FORM, userName.getParameterKind()); + + // register + JavaMethodInfo registerMethod = result.getMethods().get(3); + Assert.assertEquals("register(email : java.lang.String) : io.quarkus.qute.TemplateInstance", + registerMethod.getSignature()); + Assert.assertEquals(JaxRsMethodKind.POST, registerMethod.getJaxRsMethodKind()); + Assert.assertNotNull(registerMethod.getRestParameters()); + Assert.assertEquals(1, registerMethod.getRestParameters().size()); + + RestParam email = registerMethod.getRestParameter("email"); + Assert.assertNotNull(email); + Assert.assertEquals("email", email.getName()); + Assert.assertEquals(JaxRsParamKind.FORM, email.getParameterKind()); + + // confirm + JavaMethodInfo confirmMethod = result.getMethods().get(4); + Assert.assertEquals("confirm(confirmationCode : java.lang.String) : io.quarkus.qute.TemplateInstance", + confirmMethod.getSignature()); + Assert.assertEquals(JaxRsMethodKind.GET, confirmMethod.getJaxRsMethodKind()); + Assert.assertNotNull(confirmMethod.getRestParameters()); + Assert.assertEquals(1, confirmMethod.getRestParameters().size()); + + RestParam confirmationCode = confirmMethod.getRestParameter("confirmationCode"); + Assert.assertNotNull(confirmationCode); + Assert.assertEquals("confirmationCode", confirmationCode.getName()); + Assert.assertEquals(JaxRsParamKind.PATH, confirmationCode.getParameterKind()); + + // logoutFirst + JavaMethodInfo logoutFirstMethod = result.getMethods().get(5); + Assert.assertEquals("logoutFirst() : io.quarkus.qute.TemplateInstance", logoutFirstMethod.getSignature()); + Assert.assertEquals(JaxRsMethodKind.GET, logoutFirstMethod.getJaxRsMethodKind()); + + // complete + JavaMethodInfo completeMethod = result.getMethods().get(6); + Assert.assertEquals( + "complete(confirmationCode : java.lang.String, userName : java.lang.String, password : java.lang.String, password2 : java.lang.String, webAuthnResponse : io.quarkus.security.webauthn.WebAuthnRegisterResponse, firstName : java.lang.String, lastName : java.lang.String, ctx : io.vertx.ext.web.RoutingContext) : javax.ws.rs.core.Response", + completeMethod.getSignature()); + Assert.assertEquals(JaxRsMethodKind.POST, completeMethod.getJaxRsMethodKind()); + Assert.assertNotNull(completeMethod.getRestParameters()); + Assert.assertEquals(6, completeMethod.getRestParameters().size()); + + confirmationCode = completeMethod.getRestParameter("confirmationCode"); + Assert.assertNotNull(confirmationCode); + Assert.assertEquals("confirmationCode", confirmationCode.getName()); + Assert.assertEquals(JaxRsParamKind.QUERY, confirmationCode.getParameterKind()); + + RestParam firstName = completeMethod.getRestParameter("firstName"); + Assert.assertNotNull(firstName); + Assert.assertEquals("firstName", firstName.getName()); + Assert.assertEquals(JaxRsParamKind.FORM, firstName.getParameterKind()); + + RestParam lastName = completeMethod.getRestParameter("lastName"); + Assert.assertNotNull(lastName); + Assert.assertEquals("lastName", lastName.getName()); + Assert.assertEquals(JaxRsParamKind.FORM, lastName.getParameterKind()); + + password = completeMethod.getRestParameter("password"); + Assert.assertNotNull(password); + Assert.assertEquals("password", password.getName()); + Assert.assertEquals(JaxRsParamKind.FORM, password.getParameterKind()); + + RestParam password2 = completeMethod.getRestParameter("password2"); + Assert.assertNotNull(password2); + Assert.assertEquals("password2", password2.getName()); + Assert.assertEquals(JaxRsParamKind.FORM, password2.getParameterKind()); + + userName = completeMethod.getRestParameter("userName"); + Assert.assertNotNull(userName); + Assert.assertEquals("userName", userName.getName()); + Assert.assertEquals(JaxRsParamKind.FORM, userName.getParameterKind()); + } + private static void assertExtendedTypes(String type, String extendedType, List extendedTypes) { Assert.assertTrue("The Java type '" + type + "' should extends '" + extendedType + "'.", extendedTypes.contains(extendedType)); diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/renarde/UriNamespaceResolverSupportTest.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/renarde/UriNamespaceResolverSupportTest.java new file mode 100644 index 000000000..8da945494 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/template/renarde/UriNamespaceResolverSupportTest.java @@ -0,0 +1,95 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt.template.renarde; + +import static com.redhat.qute.jdt.QuteAssert.assertValueResolver; +import static com.redhat.qute.jdt.QuteProjectTest.getJDTUtils; +import static com.redhat.qute.jdt.QuteProjectTest.loadMavenProject; + +import java.util.List; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.junit.Assert; +import org.junit.Test; + +import com.redhat.qute.commons.datamodel.DataModelParameter; +import com.redhat.qute.commons.datamodel.DataModelProject; +import com.redhat.qute.commons.datamodel.DataModelTemplate; +import com.redhat.qute.commons.datamodel.QuteDataModelProjectParams; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverInfo; +import com.redhat.qute.jdt.QuteProjectTest.QuteMavenProjectName; +import com.redhat.qute.jdt.QuteSupportForTemplate; + +/** + * Renarde uri/uriabs namespace resolver support tests. + * + * @author Angelo ZERR + * + */ +public class UriNamespaceResolverSupportTest { + + @Test + public void quteRenarde() throws Exception { + + loadMavenProject(QuteMavenProjectName.quarkus_renarde_todo); + + QuteDataModelProjectParams params = new QuteDataModelProjectParams(QuteMavenProjectName.quarkus_renarde_todo); + DataModelProject> project = QuteSupportForTemplate.getInstance() + .getDataModelProject(params, getJDTUtils(), new NullProgressMonitor()); + Assert.assertNotNull(project); + + List resolvers = project.getValueResolvers(); + Assert.assertNotNull(resolvers); + Assert.assertFalse(resolvers.isEmpty()); + + testValueResolversFomTemplateExtension(resolvers); + testValueResolversFomUriNamespace(resolvers); + } + + private static void testValueResolversFomTemplateExtension(List resolvers) { + // from util.JavaExtensions + assertValueResolver(null, "isRecent(date : java.util.Date) : boolean", "util.JavaExtensions", resolvers); + } + + private static void testValueResolversFomUriNamespace(List resolvers) { + Assert.assertNotNull(resolvers); + Assert.assertFalse(resolvers.isEmpty()); + + // public class Application extends Controller + assertValueResolver("uri", "rest.Application", "rest.Application", // + "Application", resolvers); + assertValueResolver("uriabs", "rest.Application", "rest.Application", // + "Application", resolvers); + + // public class Login extends ControllerWithUser + assertValueResolver("uri", "rest.Login", "rest.Login", // + "Login", resolvers); + assertValueResolver("uriabs", "rest.Login", "rest.Login", // + "Login", resolvers); + + // public class Todos extends ControllerWithUser + assertValueResolver("uri", "rest.Todos", "rest.Todos", // + "Todos", resolvers); + assertValueResolver("uriabs", "rest.Todos", "rest.Todos", // + "Todos", resolvers); + + // public class RenardeSecurityController extends Controller { + assertValueResolver("uri", "io.quarkiverse.renarde.oidc.impl.RenardeSecurityController", + "io.quarkiverse.renarde.oidc.impl.RenardeSecurityController", // + "RenardeSecurityController", resolvers); + assertValueResolver("uriabs", "io.quarkiverse.renarde.oidc.impl.RenardeSecurityController", + "io.quarkiverse.renarde.oidc.impl.RenardeSecurityController", // + "RenardeSecurityController", resolvers); + + } + +} diff --git a/qute.jdt/com.redhat.qute.jdt/META-INF/MANIFEST.MF b/qute.jdt/com.redhat.qute.jdt/META-INF/MANIFEST.MF index dec97c991..4462dc1f2 100644 --- a/qute.jdt/com.redhat.qute.jdt/META-INF/MANIFEST.MF +++ b/qute.jdt/com.redhat.qute.jdt/META-INF/MANIFEST.MF @@ -22,6 +22,7 @@ Export-Package: com.redhat.qute.commons, com.redhat.qute.commons.annotations, com.redhat.qute.commons.datamodel, com.redhat.qute.commons.datamodel.resolvers, + com.redhat.qute.commons.jaxrs, com.redhat.qute.commons.usertags, com.redhat.qute.jdt, com.redhat.qute.jdt.internal.java;x-friends:="com.redhat.qute.jdt.test", diff --git a/qute.jdt/com.redhat.qute.jdt/plugin.properties b/qute.jdt/com.redhat.qute.jdt/plugin.properties index 5a2a7183f..a90540695 100644 --- a/qute.jdt/com.redhat.qute.jdt/plugin.properties +++ b/qute.jdt/com.redhat.qute.jdt/plugin.properties @@ -13,4 +13,5 @@ pluginName=JDT Qute Extension providerName=Red Hat -dataModelProviders.name=Data model providers \ No newline at end of file +dataModelProviders.name=Data model providers +resolvedJavaTypeFactoriesName=Resolved Java Type Factories \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt/plugin.xml b/qute.jdt/com.redhat.qute.jdt/plugin.xml index a9a3ab4db..30cf8d78c 100644 --- a/qute.jdt/com.redhat.qute.jdt/plugin.xml +++ b/qute.jdt/com.redhat.qute.jdt/plugin.xml @@ -5,6 +5,9 @@ + @@ -47,4 +50,17 @@ class="com.redhat.qute.jdt.internal.extensions.quarkus.InjectNamespaceResolverSupport" /> + + + + + + + + + + \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt/schema/resolvedJavaTypeFactories.exsd b/qute.jdt/com.redhat.qute.jdt/schema/resolvedJavaTypeFactories.exsd new file mode 100644 index 000000000..22e2955b0 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/schema/resolvedJavaTypeFactories.exsd @@ -0,0 +1,89 @@ + + + + + + + + + This extension point allows adding a resolved Java Type factory to create a ResolvedJavaTypeInfo from a given JDT IType. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Resolved Java Type factory + + + + + + + Name of a class that implements IResolvedJavaTypeFactory + + + + + + + + + + + + + + + + The following is an example of a resolved Java Type factory extension: + +<pre> + <extension point="com.redhat.qute.jdt.resolvedJavaTypeFactories"> + <factory class="com.redhat.qute.jdt.internal.extensions.renarde.RenardeResolvedJavaTypeFactory" /> + </extension> +</pre> + + + + + + + diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/JavaMethodInfo.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/JavaMethodInfo.java index 23256574b..a3957f780 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/JavaMethodInfo.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/JavaMethodInfo.java @@ -12,6 +12,8 @@ package com.redhat.qute.commons; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,6 +21,9 @@ import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; +import com.redhat.qute.commons.jaxrs.JaxRsMethodKind; +import com.redhat.qute.commons.jaxrs.RestParam; + /** * Java method information. * @@ -31,6 +36,10 @@ public class JavaMethodInfo extends JavaMemberInfo { private static final String IS_PREFIX = "is"; + private JaxRsMethodKind jaxRsMethodKind; + + private Map restParameters; + private transient String methodName; private transient String returnType; @@ -66,6 +75,55 @@ public boolean isVirtual() { return false; } + /** + * Returns the JAX RS method kind and null otherwise. + * + * @return the JAX RS method kind and null otherwise + */ + public JaxRsMethodKind getJaxRsMethodKind() { + return jaxRsMethodKind; + } + + /** + * Set the JAX RS method kind. + * + * @param jaxRsMethodKind the JAX RS method kind + */ + public void setJaxRsMethodKind(JaxRsMethodKind jaxRsMethodKind) { + this.jaxRsMethodKind = jaxRsMethodKind; + } + + /** + * Set the Rest parameters information. + * + * @param restParameters the Rest parameters information. + */ + public void setRestParameters(Map restParameters) { + this.restParameters = restParameters; + } + + /** + * Returns the Rest parameters information. + * + * @return the Rest parameters information. + */ + public Collection getRestParameters() { + return restParameters != null ? restParameters.values() : Collections.emptyList(); + } + + /** + * Returns the rest parameter information for the given parameter name and null + * otherwise. + * + * @param name the parameter name. + * + * @return the rest parameter information for the given parameter name and null + * otherwise. + */ + public RestParam getRestParameter(String name) { + return restParameters != null ? restParameters.get(name) : null; + } + /** * Returns the Java method signature with simple names. * @@ -159,6 +217,15 @@ public String getReturnType() { return NO_VALUE.equals(returnType) ? null : returnType; } + /** + * Returns true if the method is a void method and false otherwise. + * + * @return true if the method is a void method and false otherwise. + */ + public boolean isVoidMethod() { + return "void".equals(getReturnType()); + } + /** * Returns the Java return type. * @@ -267,42 +334,42 @@ private static List parseParameters(String signature) { if (!paramTypeParsing) { // ex query : switch (c) { - case ' ': - // ignore space - break; - case ':': - paramTypeParsing = true; - break; - default: - paramName.append(c); + case ' ': + // ignore space + break; + case ':': + paramTypeParsing = true; + break; + default: + paramName.append(c); } } else { // ex java.lang.String, switch (c) { - case ' ': - // ignore space - break; - case '<': - daemon++; - paramType.append(c); - break; - case '>': - daemon--; - paramType.append(c); - break; - case ',': - if (daemon == 0) { - parameters.add(new JavaParameterInfo(paramName.toString(), paramType.toString())); - paramName.setLength(0); - paramType.setLength(0); - paramTypeParsing = false; - daemon = 0; - } else { + case ' ': + // ignore space + break; + case '<': + daemon++; + paramType.append(c); + break; + case '>': + daemon--; + paramType.append(c); + break; + case ',': + if (daemon == 0) { + parameters.add(new JavaParameterInfo(paramName.toString(), paramType.toString())); + paramName.setLength(0); + paramType.setLength(0); + paramTypeParsing = false; + daemon = 0; + } else { + paramType.append(c); + } + break; + default: paramType.append(c); - } - break; - default: - paramType.append(c); } } } @@ -448,7 +515,6 @@ public static JavaMethodInfo applyGenericTypeInvocation(JavaMethodInfo method, M JavaTypeInfo.applyGenericTypeInvocation(returnType, genericMap, newSignature); } newMethod.setSignature(newSignature.toString()); - newMethod.setGenericMember(method); return newMethod; } diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/JavaTypeInfo.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/JavaTypeInfo.java index fa5806c8e..cc085bbe8 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/JavaTypeInfo.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/JavaTypeInfo.java @@ -142,6 +142,10 @@ public void setInvalidMethod(String methodName, InvalidMethodReason reason) { public void setInvalidMethods(Map invalidMethods) { this.invalidMethods = invalidMethods; } + + public Map getInvalidMethods() { + return invalidMethods; + } /** * Returns the java type parameters. diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/QuteResolvedJavaTypeParams.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/QuteResolvedJavaTypeParams.java index 0eb59109d..dfd919e43 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/QuteResolvedJavaTypeParams.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/QuteResolvedJavaTypeParams.java @@ -11,6 +11,8 @@ *******************************************************************************/ package com.redhat.qute.commons; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; + /** * Qute resolved java type parameters. * @@ -21,6 +23,8 @@ public class QuteResolvedJavaTypeParams { private String className; + private ValueResolverKind kind; + private String projectUri; public QuteResolvedJavaTypeParams() { @@ -28,7 +32,12 @@ public QuteResolvedJavaTypeParams() { } public QuteResolvedJavaTypeParams(String className, String projectUri) { + this(className, null, projectUri); + } + + public QuteResolvedJavaTypeParams(String className, ValueResolverKind kind, String projectUri) { setClassName(className); + setKind(kind); setProjectUri(projectUri); } @@ -48,4 +57,11 @@ public void setClassName(String className) { this.className = className; } + public ValueResolverKind getKind() { + return kind; + } + + public void setKind(ValueResolverKind kind) { + this.kind = kind; + } } diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/datamodel/resolvers/ValueResolverKind.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/datamodel/resolvers/ValueResolverKind.java index a552c1835..869bbd83a 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/datamodel/resolvers/ValueResolverKind.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/datamodel/resolvers/ValueResolverKind.java @@ -17,12 +17,13 @@ * @author datho7561 */ public enum ValueResolverKind { - TemplateData(0), // - TemplateEnum(1), // - TemplateExtensionOnMethod(2), // - TemplateExtensionOnClass(3), // - TemplateGlobal(4), // - InjectedBean(5); + TemplateData(1), // + TemplateEnum(2), // + TemplateExtensionOnMethod(3), // + TemplateExtensionOnClass(4), // + TemplateGlobal(6), // + InjectedBean(6), // + Renarde(7); private final int value; diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsMethodKind.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsMethodKind.java new file mode 100644 index 000000000..9a76d46b0 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsMethodKind.java @@ -0,0 +1,41 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.commons.jaxrs; + +/** + * JaxRs method kind. + * + * @author Angelo ZERR + */ +public enum JaxRsMethodKind { + + POST(1), // + GET(2); // TODO : manage another kind like @DELETE, @PUT. it currently represents any jax ws-rs other than @POST + + private final int value; + + JaxRsMethodKind(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static JaxRsMethodKind forValue(int value) { + JaxRsMethodKind[] allValues = JaxRsMethodKind.values(); + if (value < 1 || value > allValues.length) + throw new IllegalArgumentException("Illegal enum value: " + value); + return allValues[value - 1]; + } + +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsParamKind.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsParamKind.java new file mode 100644 index 000000000..429ba0664 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsParamKind.java @@ -0,0 +1,43 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.commons.jaxrs; + +/** + * JaxRs parameter kind. + * + * @author Angelo ZERR + */ +public enum JaxRsParamKind { + + NONE(0), // + QUERY(1), // + FORM(2), //s + PATH(3); + + private final int value; + + JaxRsParamKind(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static JaxRsParamKind forValue(int value) { + JaxRsParamKind[] allValues = JaxRsParamKind.values(); + if (value < 1 || value > allValues.length) + throw new IllegalArgumentException("Illegal enum value: " + value); + return allValues[value - 1]; + } + +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/RestParam.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/RestParam.java new file mode 100644 index 000000000..23ca659f7 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/commons/jaxrs/RestParam.java @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright (c) 2022 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.commons.jaxrs; + +/** + * Rest parameters informations. + * + * @author Angelo ZERR + * + */ +public class RestParam { + + private String name; + + private JaxRsParamKind parameterKind; + + private Boolean required; + + public RestParam(String name, JaxRsParamKind parameterKind, boolean required) { + this.name = name; + this.parameterKind = parameterKind; + this.required = required ? true : null; + } + + public String getName() { + return name; + } + + public JaxRsParamKind getParameterKind() { + return parameterKind; + } + + public boolean isRequired() { + return required != null ? required.booleanValue() : false; + } + +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/QuteSupportForTemplate.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/QuteSupportForTemplate.java index d7d196cc5..acd524667 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/QuteSupportForTemplate.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/QuteSupportForTemplate.java @@ -13,12 +13,9 @@ import static com.redhat.qute.jdt.utils.JDTTypeUtils.findType; -import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,9 +46,6 @@ import com.redhat.qute.commons.DocumentFormat; import com.redhat.qute.commons.GenerateMissingJavaMemberParams; -import com.redhat.qute.commons.InvalidMethodReason; -import com.redhat.qute.commons.JavaFieldInfo; -import com.redhat.qute.commons.JavaMethodInfo; import com.redhat.qute.commons.JavaTypeInfo; import com.redhat.qute.commons.ProjectInfo; import com.redhat.qute.commons.QuteJavaDefinitionParams; @@ -64,9 +58,9 @@ import com.redhat.qute.commons.datamodel.DataModelProject; import com.redhat.qute.commons.datamodel.DataModelTemplate; import com.redhat.qute.commons.datamodel.QuteDataModelProjectParams; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; import com.redhat.qute.commons.usertags.QuteUserTagParams; import com.redhat.qute.commons.usertags.UserTagInfo; -import com.redhat.qute.jdt.internal.resolver.AbstractTypeResolver; import com.redhat.qute.jdt.internal.resolver.ClassFileTypeResolver; import com.redhat.qute.jdt.internal.resolver.CompilationUnitTypeResolver; import com.redhat.qute.jdt.internal.resolver.ITypeResolver; @@ -74,10 +68,9 @@ import com.redhat.qute.jdt.internal.template.QuarkusIntegrationForQute; import com.redhat.qute.jdt.internal.template.TemplateGenerateMissingJavaMember; import com.redhat.qute.jdt.internal.template.TemplateDataSupport; +import com.redhat.qute.jdt.internal.template.resolvedtype.ResolvedJavaTypeFactoryRegistry; import com.redhat.qute.jdt.utils.IJDTUtils; import com.redhat.qute.jdt.utils.JDTQuteProjectUtils; -import com.redhat.qute.jdt.utils.JDTTypeUtils; -import com.redhat.qute.jdt.utils.QuteReflectionAnnotationUtils; /** * @@ -90,8 +83,6 @@ public class QuteSupportForTemplate { private static final Logger LOGGER = Logger.getLogger(QuteSupportForTemplate.class.getName()); - private static final String JAVA_LANG_OBJECT = "java.lang.Object"; - private static final QuteSupportForTemplate INSTANCE = new QuteSupportForTemplate(); public static QuteSupportForTemplate getInstance() { @@ -318,65 +309,8 @@ public ResolvedJavaTypeInfo getResolvedJavaType(QuteResolvedJavaTypeParams param return null; } - ITypeResolver typeResolver = createTypeResolver(type); - - // 1) Collect fields - List fieldsInfo = new ArrayList<>(); - - // Standard fields - IField[] fields = type.getFields(); - for (IField field : fields) { - if (isValidField(field, type)) { - // Only public fields are available - JavaFieldInfo info = new JavaFieldInfo(); - info.setSignature(typeResolver.resolveFieldSignature(field)); - fieldsInfo.add(info); - } - } - - // Record fields - if (type.isRecord()) { - for (IField field : type.getRecordComponents()) { - // All record components are valid - JavaFieldInfo info = new JavaFieldInfo(); - info.setSignature(typeResolver.resolveFieldSignature(field)); - fieldsInfo.add(info); - } - } - - // 2) Collect methods - List methodsInfo = new ArrayList<>(); - Map invalidMethods = new HashMap<>(); - IMethod[] methods = type.getMethods(); - for (IMethod method : methods) { - if (isValidMethod(method, type)) { - try { - InvalidMethodReason invalid = getValidMethodForQute(method, typeName); - if (invalid != null) { - invalidMethods.put(method.getElementName(), invalid); - } else { - JavaMethodInfo info = new JavaMethodInfo(); - info.setSignature(typeResolver.resolveMethodSignature(method)); - methodsInfo.add(info); - } - } catch (Exception e) { - LOGGER.log(Level.SEVERE, - "Error while getting method signature of '" + method.getElementName() + "'.", e); - } - } - } - - ResolvedJavaTypeInfo resolvedType = new ResolvedJavaTypeInfo(); - String typeSignature = AbstractTypeResolver.resolveJavaTypeSignature(type); - resolvedType.setBinary(type.isBinary()); - resolvedType.setSignature(typeSignature); - resolvedType.setFields(fieldsInfo); - resolvedType.setMethods(methodsInfo); - resolvedType.setInvalidMethods(invalidMethods); - resolvedType.setExtendedTypes(typeResolver.resolveExtendedType()); - resolvedType.setJavaTypeKind(JDTTypeUtils.getJavaTypeKind(type)); - QuteReflectionAnnotationUtils.collectAnnotations(resolvedType, type, typeResolver); - return resolvedType; + ValueResolverKind kind = params.getKind(); + return ResolvedJavaTypeFactoryRegistry.getInstance().create(type, kind); } private static boolean isValidField(IField field, IType type) throws JavaModelException { @@ -386,48 +320,6 @@ private static boolean isValidField(IField field, IType type) throws JavaModelEx return Flags.isPublic(field.getFlags()); } - private static boolean isValidMethod(IMethod method, IType type) { - try { - if (method.isConstructor() || !method.exists() || Flags.isSynthetic(method.getFlags())) { - return false; - } - if (!type.isInterface() && !Flags.isPublic(method.getFlags())) { - return false; - } - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Error while checking if '" + method.getElementName() + "' is valid.", e); - return false; - } - return true; - } - - /** - * Returns the reason - * - * @param method - * @param type - * - * @return - * - * @see https://github.com/quarkusio/quarkus/blob/ce19ff75e9f732ff731bb30c2141b44b42c66050/independent-projects/qute/core/src/main/java/io/quarkus/qute/ReflectionValueResolver.java#L176 - */ - private static InvalidMethodReason getValidMethodForQute(IMethod method, String typeName) { - if (JAVA_LANG_OBJECT.equals(typeName)) { - return InvalidMethodReason.FromObject; - } - try { - if ("V".equals(method.getReturnType())) { - return InvalidMethodReason.VoidReturn; - } - if (Flags.isStatic(method.getFlags())) { - return InvalidMethodReason.Static; - } - } catch (JavaModelException e) { - LOGGER.log(Level.SEVERE, "Error while checking if '" + method.getElementName() + "' is valid.", e); - } - return null; - } - private static IJavaProject getJavaProjectFromProjectUri(String projectName) { if (projectName == null) { return null; @@ -506,7 +398,8 @@ public String getJavadoc(QuteJavadocParams params, IJDTUtils utils, IProgressMon } } - private String getJavadoc(IType type, DocumentFormat documentFormat, String memberName, String signature, IJDTUtils utils, IProgressMonitor monitor, Set visited) throws JavaModelException { + private String getJavadoc(IType type, DocumentFormat documentFormat, String memberName, String signature, + IJDTUtils utils, IProgressMonitor monitor, Set visited) throws JavaModelException { if (visited.contains(type)) { return null; } @@ -522,8 +415,7 @@ private String getJavadoc(IType type, DocumentFormat documentFormat, String memb // Standard fields IField[] fields = type.getFields(); for (IField field : fields) { - if (isValidField(field, type) - && memberName.equals(field.getElementName()) + if (isValidField(field, type) && memberName.equals(field.getElementName()) && signature.equals(typeResolver.resolveFieldSignature(field))) { String javadoc = utils.getJavadoc(field, documentFormat); if (javadoc != null) { @@ -549,20 +441,17 @@ private String getJavadoc(IType type, DocumentFormat documentFormat, String memb // 2) Check the methods for the member IMethod[] methods = type.getMethods(); for (IMethod method : methods) { - if (isValidMethod(method, type)) { - try { - InvalidMethodReason invalid = getValidMethodForQute(method, type.getFullyQualifiedName()); - if (invalid == null && (signature.equals(typeResolver.resolveMethodSignature(method)))) { - String javadoc = utils.getJavadoc(method, documentFormat); - if (javadoc != null) { - return javadoc; - } - // otherwise, maybe a supertype has it + try { + if (signature.equals(typeResolver.resolveMethodSignature(method))) { + String javadoc = utils.getJavadoc(method, documentFormat); + if (javadoc != null) { + return javadoc; } - } catch (Exception e) { - LOGGER.log(Level.SEVERE, - "Error while getting method signature of '" + method.getElementName() + "'.", e); + // otherwise, maybe a supertype has it } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while getting method signature of '" + method.getElementName() + "'.", + e); } } @@ -584,7 +473,8 @@ private String getJavadoc(IType type, DocumentFormat documentFormat, String memb if (extendedTypes != null) { for (IType extendedType : extendedTypes) { - String javadoc = getJavadoc(extendedType, documentFormat, memberName, signature, utils, monitor, visited); + String javadoc = getJavadoc(extendedType, documentFormat, memberName, signature, utils, monitor, + visited); if (javadoc != null) { return javadoc; } @@ -595,7 +485,8 @@ private String getJavadoc(IType type, DocumentFormat documentFormat, String memb } - private IType getTypeFromParams(String typeName, String projectUri, IProgressMonitor monitor) throws JavaModelException { + private IType getTypeFromParams(String typeName, String projectUri, IProgressMonitor monitor) + throws JavaModelException { IJavaProject javaProject = getJavaProjectFromProjectUri(projectUri); if (javaProject == null) { return null; diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/AbstractQuteExtensionPointRegistry.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/AbstractQuteExtensionPointRegistry.java index 9723d22a0..84f24ee36 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/AbstractQuteExtensionPointRegistry.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/AbstractQuteExtensionPointRegistry.java @@ -108,7 +108,7 @@ public void registryChanged(final IRegistryChangeEvent event) { private void addExtensionProviders(IConfigurationElement[] cf) { for (IConfigurationElement ce : cf) { try { - T provider = createProvider(ce); + T provider = createInstance(ce); synchronized (providers) { providers.add(provider); } @@ -119,14 +119,14 @@ private void addExtensionProviders(IConfigurationElement[] cf) { } } - protected T createProvider(IConfigurationElement ce) throws CoreException { + protected T createInstance(IConfigurationElement ce) throws CoreException { return (T) ce.createExecutableExtension(CLASS_ATTR); } private void removeExtensionProviders(IConfigurationElement[] cf) { for (IConfigurationElement ce : cf) { try { - T provider = createProvider(ce); + T provider = createInstance(ce); synchronized (providers) { providers.remove(provider); } diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java index 6396c2136..0a755ed4e 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java @@ -19,6 +19,8 @@ */ public class QuteJavaConstants { + public static final String JAVA_LANG_OBJECT_TYPE = "java.lang.Object"; + public static final String JAVAX_INJECT_NAMED_ANNOTATION = "javax.inject.Named"; public static final String LOCATION_ANNOTATION = "io.quarkus.qute.Location"; diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/RenardeJavaConstants.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/RenardeJavaConstants.java new file mode 100644 index 000000000..d0084573b --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/RenardeJavaConstants.java @@ -0,0 +1,24 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt.internal.extensions.renarde; + +/** + * Renarde Java constants. + * + * @author Angelo ZERR + * + */ +public class RenardeJavaConstants { + + public static final String RENARDE_CONTROLLER_TYPE = "io.quarkiverse.renarde.Controller"; + +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/RenardeResolvedJavaTypeFactory.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/RenardeResolvedJavaTypeFactory.java new file mode 100644 index 000000000..d33fbf606 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/RenardeResolvedJavaTypeFactory.java @@ -0,0 +1,161 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt.internal.extensions.renarde; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.core.IAnnotation; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.ILocalVariable; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; + +import com.redhat.qute.commons.InvalidMethodReason; +import com.redhat.qute.commons.JavaMethodInfo; +import com.redhat.qute.commons.ResolvedJavaTypeInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; +import com.redhat.qute.commons.jaxrs.JaxRsMethodKind; +import com.redhat.qute.commons.jaxrs.JaxRsParamKind; +import com.redhat.qute.commons.jaxrs.RestParam; +import com.redhat.qute.jdt.internal.resolver.ITypeResolver; +import com.redhat.qute.jdt.internal.template.resolvedtype.AbstractResolvedJavaTypeFactory; +import com.redhat.qute.jdt.utils.AnnotationUtils; + +/** + * Custom Factory to create an {@link ResolvedJavaTypeInfo} instance for Renarde + * controller. + * + * @author Angelo ZERR + * + */ +public class RenardeResolvedJavaTypeFactory extends AbstractResolvedJavaTypeFactory { + + private static final String JAVAX_WS_RS_POST_ANNOTATION = "javax.ws.rs.POST"; + private static final String JAKARTA_WS_RS_POST_ANNOTATION = "jakarta.ws.rs.POST"; + + private static final String REST_FORM_ANNOTATION = "org.jboss.resteasy.reactive.RestForm"; + + private static final String JAVAX_WS_RS_FORM_PARAM_ANNOTATION = "javax.ws.rs.FormParam"; + + private static final String JAKARTA_WS_RS_FORM_PARAM_ANNOTATION = "jakarta.ws.rs.FormParam"; + + private static final String REST_PATH_ANNOTATION = "org.jboss.resteasy.reactive.RestPath"; + + private static final String JAVAX_WS_RS_PATH_PARAM_ANNOTATION = "javax.ws.rs.PathParam"; + + private static final String JAKARTA_WS_RS_PATH_PARAM_ANNOTATION = "jakarta.ws.rs.PathParam"; + + private static final String REST_QUERY_ANNOTATION = "org.jboss.resteasy.reactive.RestQuery"; + + private static final String JAVAX_WS_RS_QUERY_PARAM_ANNOTATION = "javax.ws.rs.QueryParam"; + + private static final String JAKARTA_WS_RS_QUERY_PARAM_ANNOTATION = "jakarta.ws.rs.QueryParam"; + + @Override + public boolean isAdaptedFor(ValueResolverKind kind) { + return kind == ValueResolverKind.Renarde; + } + + @Override + protected boolean isValidField(IField field, IType type) throws JavaModelException { + return false; + } + + @Override + protected boolean isValidRecordField(IField field, IType type) { + return false; + } + + @Override + protected InvalidMethodReason getValidMethodForQute(IMethod method, String typeName) { + return null; + } + + @Override + protected JavaMethodInfo createMethod(IMethod method, ITypeResolver typeResolver) { + JavaMethodInfo info = super.createMethod(method, typeResolver); + collectJaxrsInfo(method, info); + return info; + } + + private static void collectJaxrsInfo(IMethod method, JavaMethodInfo info) { + // By default all public methods are GET + JaxRsMethodKind methodKind = JaxRsMethodKind.GET; + // TODO : we support only @POST, we need to support @PUT, @DELETE, when we will need it. + if (isPostMethod(method)) { + methodKind = JaxRsMethodKind.POST; + } + info.setJaxRsMethodKind(methodKind); + try { + Map restParameters = null; + ILocalVariable[] parameters = method.getParameters(); + for (ILocalVariable parameter : parameters) { + // @RestForm, @FormParam + IAnnotation formAnnotation = AnnotationUtils.getAnnotation(parameter, REST_FORM_ANNOTATION, + JAVAX_WS_RS_FORM_PARAM_ANNOTATION, JAKARTA_WS_RS_FORM_PARAM_ANNOTATION); + if (formAnnotation != null) { + if (restParameters == null) { + restParameters = new HashMap<>(); + } + fillRestParam(parameter, formAnnotation, JaxRsParamKind.FORM, restParameters); + } else { + // @RestPath, @PathParam + IAnnotation pathAnnotation = AnnotationUtils.getAnnotation(parameter, REST_PATH_ANNOTATION, + JAVAX_WS_RS_PATH_PARAM_ANNOTATION, JAKARTA_WS_RS_PATH_PARAM_ANNOTATION); + if (pathAnnotation != null) { + if (restParameters == null) { + restParameters = new HashMap<>(); + } + fillRestParam(parameter, pathAnnotation, JaxRsParamKind.PATH, restParameters); + } else { + // @RestQuery, @QueryParam + IAnnotation queryAnnotation = AnnotationUtils.getAnnotation(parameter, REST_QUERY_ANNOTATION, + JAVAX_WS_RS_QUERY_PARAM_ANNOTATION, JAKARTA_WS_RS_QUERY_PARAM_ANNOTATION); + if (queryAnnotation != null) { + if (restParameters == null) { + restParameters = new HashMap<>(); + } + fillRestParam(parameter, queryAnnotation, JaxRsParamKind.QUERY, restParameters); + } + } + } + } + if (restParameters != null) { + info.setRestParameters(restParameters); + } + } catch (Exception e) { + + } + } + + private static void fillRestParam(ILocalVariable parameter, IAnnotation formAnnotation, + JaxRsParamKind parameterKind, Map restParameters) throws JavaModelException { + String parameterName = parameter.getElementName(); + String formName = parameterName; + String value = AnnotationUtils.getAnnotationMemberValue(formAnnotation, "value"); + if (value != null) { + formName = value; + } + restParameters.put(parameterName, new RestParam(formName, parameterKind, false)); + } + + private static boolean isPostMethod(IMethod method) { + try { + return AnnotationUtils.hasAnnotation(method, JAVAX_WS_RS_POST_ANNOTATION, JAKARTA_WS_RS_POST_ANNOTATION); + } catch (JavaModelException e) { + return false; + } + } + +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/UriNamespaceResolverSupport.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/UriNamespaceResolverSupport.java new file mode 100644 index 000000000..bde50e002 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/extensions/renarde/UriNamespaceResolverSupport.java @@ -0,0 +1,145 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt.internal.extensions.renarde; + +import static com.redhat.qute.jdt.internal.QuteJavaConstants.JAVA_LANG_OBJECT_TYPE; +import static com.redhat.qute.jdt.internal.extensions.renarde.RenardeJavaConstants.RENARDE_CONTROLLER_TYPE; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeHierarchy; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchPattern; + +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; +import com.redhat.qute.jdt.template.datamodel.AbstractDataModelProvider; +import com.redhat.qute.jdt.template.datamodel.SearchContext; +import com.redhat.qute.jdt.utils.JDTTypeUtils; + +/** + * uri, uriabs renarde support. + * + * @author Angelo ZERR + * + * @see https://github.com/quarkiverse/quarkus-renarde/blob/main/docs/modules/ROOT/pages/index.adoc#obtaining-a-uri-in-qute-views + * + */ +public class UriNamespaceResolverSupport extends AbstractDataModelProvider { + + private static final Logger LOGGER = Logger.getLogger(UriNamespaceResolverSupport.class.getName()); + + private static final String URI_NAMESPACE = "uri"; + + private static final String URIABS_NAMESPACE = "uriabs"; + + @Override + public void beginSearch(SearchContext context, IProgressMonitor monitor) { + IJavaProject javaProject = context.getJavaProject(); + IType type = JDTTypeUtils.findType(javaProject, RENARDE_CONTROLLER_TYPE); + if (type != null) { + try { + // Find all classes which extends 'io.quarkiverse.renarde.Controller' + collectRenardeController(type, context, monitor); + } catch (JavaModelException e) { + LOGGER.log(Level.SEVERE, "Error while collecting Renarde Controller.", e); + } + + } + } + + public void collectRenardeController(IType type, SearchContext context, IProgressMonitor monitor) + throws JavaModelException { + if (type == null) { + return; + } + ITypeHierarchy typeHierarchy = type.newTypeHierarchy(monitor); + IType[] controllerTypes = typeHierarchy.getAllSubtypes(type); + List resolvers = context.getDataModelProject().getValueResolvers(); + for (IType controllerType : controllerTypes) { + if (isRenardeController(controllerType)) { + addRenardeController(URI_NAMESPACE, controllerType, resolvers); + addRenardeController(URIABS_NAMESPACE, controllerType, resolvers); + } + } + } + + /** + * Returns true if the given Java type is a non abstract Renarde controller and + * false otherwise. + * + * @param controllerType the renarde controller type. + * + * @return true if the given Java type is a non abstract Renarde controller and + * false otherwise. + * @throws JavaModelException + */ + private static boolean isRenardeController(IType controllerType) throws JavaModelException { + if (Flags.isAbstract(controllerType.getFlags())) { + return false; + } + String typeName = controllerType.getFullyQualifiedName(); + return !(JAVA_LANG_OBJECT_TYPE.equals(typeName) || RENARDE_CONTROLLER_TYPE.equals(typeName)); + } + + /** + * Add renarde controller as Qute resolver. + * + * @param namespace the uri, uriabs renarde namespace. + * @param controllerType the controller type. + * @param resolvers the resolvers to fill. + */ + private static void addRenardeController(String namespace, IType controllerType, + List resolvers) { + String className = controllerType.getFullyQualifiedName(); + String named = controllerType.getElementName(); + ValueResolverInfo resolver = new ValueResolverInfo(); + resolver.setNamed(named); + resolver.setSourceType(className); + resolver.setSignature(className); + resolver.setNamespace(namespace); + resolver.setKind(ValueResolverKind.Renarde); + if (!resolvers.contains(resolver)) { + resolvers.add(resolver); + } + } + + @Override + protected boolean isNamespaceAvailable(String namespace, SearchContext context, IProgressMonitor monitor) { + // uri, and uriabs are available only for renarde project + IJavaProject javaProject = context.getJavaProject(); + return JDTTypeUtils.findType(javaProject, RENARDE_CONTROLLER_TYPE) != null; + } + + @Override + public void collectDataModel(SearchMatch match, SearchContext context, IProgressMonitor monitor) { + // Do nothing + } + + @Override + protected String[] getPatterns() { + return null; + } + + @Override + protected SearchPattern createSearchPattern(String pattern) { + return null; + } + +} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/ls/QuteSupportForTemplateDelegateCommandHandler.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/ls/QuteSupportForTemplateDelegateCommandHandler.java index 6f48146a1..01125ed6c 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/ls/QuteSupportForTemplateDelegateCommandHandler.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/ls/QuteSupportForTemplateDelegateCommandHandler.java @@ -43,6 +43,7 @@ import com.redhat.qute.commons.datamodel.DataModelProject; import com.redhat.qute.commons.datamodel.DataModelTemplate; import com.redhat.qute.commons.datamodel.QuteDataModelProjectParams; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; import com.redhat.qute.commons.usertags.QuteUserTagParams; import com.redhat.qute.commons.usertags.UserTagInfo; import com.redhat.qute.jdt.QuteSupportForTemplate; @@ -246,7 +247,13 @@ private static QuteResolvedJavaTypeParams createQuteResolvedJavaTypeParams(List< throw new UnsupportedOperationException(String.format( "Command '%s' must be called with required QuteResolvedJavaTypeParams.className!", commandId)); } - return new QuteResolvedJavaTypeParams(className, projectUri); + ValueResolverKind kind = null; + try { + kind = ValueResolverKind.forValue(ArgumentUtils.getInt(obj, "kind")); + } catch (IllegalArgumentException e) { + // ignored + } + return new QuteResolvedJavaTypeParams(className, kind, projectUri); } private static List getJavaTypes(List arguments, String commandId, IProgressMonitor monitor) diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/TemplateDataCollector.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/TemplateDataCollector.java index c7f3681e7..b23648af7 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/TemplateDataCollector.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/TemplateDataCollector.java @@ -11,6 +11,8 @@ *******************************************************************************/ package com.redhat.qute.jdt.internal.template; +import static com.redhat.qute.jdt.internal.QuteJavaConstants.JAVA_LANG_OBJECT_TYPE; + import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ITypeBinding; @@ -61,7 +63,7 @@ protected boolean visitParameter(Object name, Object type) { paramName = ((StringLiteral) name).getLiteralValue(); } if (paramName != null) { - String paramType = "java.lang.Object"; + String paramType = JAVA_LANG_OBJECT_TYPE; if (type instanceof Expression) { ITypeBinding binding = ((Expression) type).resolveTypeBinding(); paramType = binding.getQualifiedName(); diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/DataModelProviderRegistry.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/DataModelProviderRegistry.java index 7385838a1..af82838a6 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/DataModelProviderRegistry.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/DataModelProviderRegistry.java @@ -80,8 +80,8 @@ public String getProviderExtensionId() { } @Override - protected IDataModelProvider createProvider(IConfigurationElement ce) throws CoreException { - IDataModelProvider provider = super.createProvider(ce); + protected IDataModelProvider createInstance(IConfigurationElement ce) throws CoreException { + IDataModelProvider provider = super.createInstance(ce); String namespaces = ce.getAttribute(NAMESPACES_ATTR); if (StringUtils.isNotEmpty(namespaces)) { String description = ce.getAttribute(DESCRIPTION_ATTR); diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/AbstractResolvedJavaTypeFactory.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/AbstractResolvedJavaTypeFactory.java new file mode 100644 index 000000000..bf6421e0b --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/AbstractResolvedJavaTypeFactory.java @@ -0,0 +1,139 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt.internal.template.resolvedtype; + +import static com.redhat.qute.jdt.QuteSupportForTemplate.createTypeResolver; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; + +import com.redhat.qute.commons.InvalidMethodReason; +import com.redhat.qute.commons.JavaFieldInfo; +import com.redhat.qute.commons.JavaMethodInfo; +import com.redhat.qute.commons.ResolvedJavaTypeInfo; +import com.redhat.qute.jdt.internal.resolver.AbstractTypeResolver; +import com.redhat.qute.jdt.internal.resolver.ITypeResolver; +import com.redhat.qute.jdt.utils.JDTTypeUtils; +import com.redhat.qute.jdt.utils.QuteReflectionAnnotationUtils; + +/** + * Abstract class for {@link ResolvedJavaTypeInfo} factory. + * + * @author Angelo ZERR + * + */ +public abstract class AbstractResolvedJavaTypeFactory implements IResolvedJavaTypeFactory { + + private static final Logger LOGGER = Logger.getLogger(AbstractResolvedJavaTypeFactory.class.getName()); + + @Override + public ResolvedJavaTypeInfo create(IType type) throws CoreException { + ITypeResolver typeResolver = createTypeResolver(type); + String typeSignature = AbstractTypeResolver.resolveJavaTypeSignature(type); + + // 1) Collect fields + List fieldsInfo = new ArrayList<>(); + + // Standard fields + IField[] fields = type.getFields(); + for (IField field : fields) { + if (isValidField(field, type)) { + // Only public fields are available + JavaFieldInfo info = new JavaFieldInfo(); + info.setSignature(typeResolver.resolveFieldSignature(field)); + fieldsInfo.add(info); + } + } + + // Record fields + if (type.isRecord()) { + for (IField field : type.getRecordComponents()) { + if (isValidRecordField(field, type)) { + // All record components are valid + JavaFieldInfo info = new JavaFieldInfo(); + info.setSignature(typeResolver.resolveFieldSignature(field)); + fieldsInfo.add(info); + } + } + } + + // 2) Collect methods + List methodsInfo = new ArrayList<>(); + Map invalidMethods = new HashMap<>(); + IMethod[] methods = type.getMethods(); + for (IMethod method : methods) { + if (isValidMethod(method, type)) { + try { + InvalidMethodReason invalid = getValidMethodForQute(method, typeSignature); + if (invalid != null) { + invalidMethods.put(method.getElementName(), invalid); + } else { + JavaMethodInfo info = createMethod(method, typeResolver); + methodsInfo.add(info); + } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, + "Error while getting method signature of '" + method.getElementName() + "'.", e); + } + } + } + + ResolvedJavaTypeInfo resolvedType = new ResolvedJavaTypeInfo(); + resolvedType.setBinary(type.isBinary()); + resolvedType.setSignature(typeSignature); + resolvedType.setFields(fieldsInfo); + resolvedType.setMethods(methodsInfo); + resolvedType.setInvalidMethods(invalidMethods); + resolvedType.setExtendedTypes(typeResolver.resolveExtendedType()); + resolvedType.setJavaTypeKind(JDTTypeUtils.getJavaTypeKind(type)); + QuteReflectionAnnotationUtils.collectAnnotations(resolvedType, type, typeResolver); + return resolvedType; + } + + protected JavaMethodInfo createMethod(IMethod method, ITypeResolver typeResolver) { + JavaMethodInfo info = new JavaMethodInfo(); + info.setSignature(typeResolver.resolveMethodSignature(method)); + return info; + } + + protected abstract boolean isValidRecordField(IField field, IType type); + + protected abstract boolean isValidField(IField field, IType type) throws JavaModelException; + + protected boolean isValidMethod(IMethod method, IType type) { + try { + if (method.isConstructor() || !method.exists() || Flags.isSynthetic(method.getFlags())) { + return false; + } + if (!type.isInterface() && !Flags.isPublic(method.getFlags())) { + return false; + } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while checking if '" + method.getElementName() + "' is valid.", e); + return false; + } + return true; + } + + protected abstract InvalidMethodReason getValidMethodForQute(IMethod method, String typeName); +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/DefaultResolvedJavaTypeFactory.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/DefaultResolvedJavaTypeFactory.java new file mode 100644 index 000000000..855d56805 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/DefaultResolvedJavaTypeFactory.java @@ -0,0 +1,90 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt.internal.template.resolvedtype; + +import static com.redhat.qute.jdt.internal.QuteJavaConstants.JAVA_LANG_OBJECT_TYPE; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; + +import com.redhat.qute.commons.InvalidMethodReason; +import com.redhat.qute.commons.ResolvedJavaTypeInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; + +/** + * The default {@link ResolvedJavaTypeInfo} factory. + * + * @author Angelo ZERR + * + */ +public class DefaultResolvedJavaTypeFactory extends AbstractResolvedJavaTypeFactory { + + private static final Logger LOGGER = Logger.getLogger(DefaultResolvedJavaTypeFactory.class.getName()); + + private static final IResolvedJavaTypeFactory INSTANCE = new DefaultResolvedJavaTypeFactory(); + + public static IResolvedJavaTypeFactory getInstance() { + return INSTANCE; + } + + @Override + public boolean isAdaptedFor(ValueResolverKind kind) { + return true; + } + + @Override + protected boolean isValidField(IField field, IType type) throws JavaModelException { + if (type.isEnum()) { + return true; + } + return Flags.isPublic(field.getFlags()); + } + + @Override + protected boolean isValidRecordField(IField field, IType type) { + return true; + } + + /** + * Returns the reason + * + * @param method + * @param type + * + * @return + * + * @see https://github.com/quarkusio/quarkus/blob/ce19ff75e9f732ff731bb30c2141b44b42c66050/independent-projects/qute/core/src/main/java/io/quarkus/qute/ReflectionValueResolver.java#L176 + */ + @Override + protected InvalidMethodReason getValidMethodForQute(IMethod method, String typeName) { + if (JAVA_LANG_OBJECT_TYPE.equals(typeName)) { + return InvalidMethodReason.FromObject; + } + try { + if ("V".equals(method.getReturnType())) { + return InvalidMethodReason.VoidReturn; + } + if (Flags.isStatic(method.getFlags())) { + return InvalidMethodReason.Static; + } + } catch (JavaModelException e) { + LOGGER.log(Level.SEVERE, "Error while checking if '" + method.getElementName() + "' is valid.", e); + } + return null; + } +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/IResolvedJavaTypeFactory.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/IResolvedJavaTypeFactory.java new file mode 100644 index 000000000..390cc8185 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/IResolvedJavaTypeFactory.java @@ -0,0 +1,52 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt.internal.template.resolvedtype; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IType; + +import com.redhat.qute.commons.ResolvedJavaTypeInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; + +/** + * API to create {@link ResolvedJavaTypeInfo} from a given JDT {@link IType}. + * + * @author Angelo ZERR + * + */ +public interface IResolvedJavaTypeFactory { + + /** + * Returns true if the factory can deal the given value resolver kind and false + * otherwise. + * + * @param kind the value resolver kind. + * + * @return true if the factory can deal the given value resolver kind and false + * otherwise. + */ + boolean isAdaptedFor(ValueResolverKind kind); + + /** + * Returns an instance of {@link ResolvedJavaTypeInfo} from the given JDT + * {@link IType}. + * + * @param type the JDT Java type. + * + * @return an instance of {@link ResolvedJavaTypeInfo} from the given JDT + * {@link IType}. + * + * @throws CoreException + */ + ResolvedJavaTypeInfo create(IType type) throws CoreException; + +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/ResolvedJavaTypeFactoryRegistry.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/ResolvedJavaTypeFactoryRegistry.java new file mode 100644 index 000000000..266d535ed --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/resolvedtype/ResolvedJavaTypeFactoryRegistry.java @@ -0,0 +1,59 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +* which is available at https://www.apache.org/licenses/LICENSE-2.0. +* +* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.jdt.internal.template.resolvedtype; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IType; + +import com.redhat.qute.commons.ResolvedJavaTypeInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; +import com.redhat.qute.jdt.internal.AbstractQuteExtensionPointRegistry; + +/** + * Registry to handle instances of {@link IResolvedJavaTypeFactory} + * + * @author Angelo ZERR + */ +public class ResolvedJavaTypeFactoryRegistry extends AbstractQuteExtensionPointRegistry { + + private static final String RESOLVED_JAVA_TYPE_FACTORIES_EXTENSION_POINT_ID = "resolvedJavaTypeFactories"; + + private static final ResolvedJavaTypeFactoryRegistry INSTANCE = new ResolvedJavaTypeFactoryRegistry(); + + private ResolvedJavaTypeFactoryRegistry() { + super(); + } + + public static ResolvedJavaTypeFactoryRegistry getInstance() { + return INSTANCE; + } + + @Override + public String getProviderExtensionId() { + return RESOLVED_JAVA_TYPE_FACTORIES_EXTENSION_POINT_ID; + } + + public ResolvedJavaTypeInfo create(IType type, ValueResolverKind kind) throws CoreException { + return getFactory(kind).create(type); + } + + private IResolvedJavaTypeFactory getFactory(ValueResolverKind kind) { + for (IResolvedJavaTypeFactory factory : getProviders()) { + if (factory.isAdaptedFor(kind)) { + return factory; + } + } + return DefaultResolvedJavaTypeFactory.getInstance(); + } +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/template/datamodel/AbstractDataModelProvider.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/template/datamodel/AbstractDataModelProvider.java index 25fadb66d..3f3c2a67d 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/template/datamodel/AbstractDataModelProvider.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/template/datamodel/AbstractDataModelProvider.java @@ -112,8 +112,25 @@ public void endSearch(SearchContext context, IProgressMonitor monitor) { if (info != null) { // Register namespace information String namespacekey = info.getNamespaces().get(0); - Map infos = context.getDataModelProject().getNamespaceResolverInfos(); - infos.put(namespacekey, info); + if (isNamespaceAvailable(namespacekey, context, monitor)) { + Map infos = context.getDataModelProject().getNamespaceResolverInfos(); + infos.put(namespacekey, info); + } } } + + /** + * Returns true if the given namespace is available for the java project and + * false otherwise. + * + * @param namespace the namespace. + * @param context the search context. + * @param monitor the progress monitor. + * + * @return true if the given namespace is available for the java project and + * false otherwise. + */ + protected boolean isNamespaceAvailable(String namespace, SearchContext context, IProgressMonitor monitor) { + return true; + } } diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/AnnotationUtils.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/AnnotationUtils.java index 7dadef470..fcfb3980a 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/AnnotationUtils.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/AnnotationUtils.java @@ -37,8 +37,8 @@ public class AnnotationUtils { private static final String ATTRIBUTE_VALUE = "value"; - public static boolean hasAnnotation(IAnnotatable annotatable, String annotationName) throws JavaModelException { - return getAnnotation(annotatable, annotationName) != null; + public static boolean hasAnnotation(IAnnotatable annotatable, String... annotationNames) throws JavaModelException { + return getAnnotation(annotatable, annotationNames) != null; } /** @@ -51,14 +51,17 @@ public static boolean hasAnnotation(IAnnotatable annotatable, String annotationN * the given name annotationName and null otherwise. * @throws JavaModelException */ - public static IAnnotation getAnnotation(IAnnotatable annotatable, String annotationName) throws JavaModelException { + public static IAnnotation getAnnotation(IAnnotatable annotatable, String... annotationNames) + throws JavaModelException { if (annotatable == null) { return null; } IAnnotation[] annotations = annotatable.getAnnotations(); for (IAnnotation annotation : annotations) { - if (isMatchAnnotation(annotation, annotationName)) { - return annotation; + for (String annotationName : annotationNames) { + if (isMatchAnnotation(annotation, annotationName)) { + return annotation; + } } } return null; diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/JavaMethodInfo.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/JavaMethodInfo.java index 23256574b..a3957f780 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/JavaMethodInfo.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/JavaMethodInfo.java @@ -12,6 +12,8 @@ package com.redhat.qute.commons; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,6 +21,9 @@ import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; +import com.redhat.qute.commons.jaxrs.JaxRsMethodKind; +import com.redhat.qute.commons.jaxrs.RestParam; + /** * Java method information. * @@ -31,6 +36,10 @@ public class JavaMethodInfo extends JavaMemberInfo { private static final String IS_PREFIX = "is"; + private JaxRsMethodKind jaxRsMethodKind; + + private Map restParameters; + private transient String methodName; private transient String returnType; @@ -66,6 +75,55 @@ public boolean isVirtual() { return false; } + /** + * Returns the JAX RS method kind and null otherwise. + * + * @return the JAX RS method kind and null otherwise + */ + public JaxRsMethodKind getJaxRsMethodKind() { + return jaxRsMethodKind; + } + + /** + * Set the JAX RS method kind. + * + * @param jaxRsMethodKind the JAX RS method kind + */ + public void setJaxRsMethodKind(JaxRsMethodKind jaxRsMethodKind) { + this.jaxRsMethodKind = jaxRsMethodKind; + } + + /** + * Set the Rest parameters information. + * + * @param restParameters the Rest parameters information. + */ + public void setRestParameters(Map restParameters) { + this.restParameters = restParameters; + } + + /** + * Returns the Rest parameters information. + * + * @return the Rest parameters information. + */ + public Collection getRestParameters() { + return restParameters != null ? restParameters.values() : Collections.emptyList(); + } + + /** + * Returns the rest parameter information for the given parameter name and null + * otherwise. + * + * @param name the parameter name. + * + * @return the rest parameter information for the given parameter name and null + * otherwise. + */ + public RestParam getRestParameter(String name) { + return restParameters != null ? restParameters.get(name) : null; + } + /** * Returns the Java method signature with simple names. * @@ -159,6 +217,15 @@ public String getReturnType() { return NO_VALUE.equals(returnType) ? null : returnType; } + /** + * Returns true if the method is a void method and false otherwise. + * + * @return true if the method is a void method and false otherwise. + */ + public boolean isVoidMethod() { + return "void".equals(getReturnType()); + } + /** * Returns the Java return type. * @@ -267,42 +334,42 @@ private static List parseParameters(String signature) { if (!paramTypeParsing) { // ex query : switch (c) { - case ' ': - // ignore space - break; - case ':': - paramTypeParsing = true; - break; - default: - paramName.append(c); + case ' ': + // ignore space + break; + case ':': + paramTypeParsing = true; + break; + default: + paramName.append(c); } } else { // ex java.lang.String, switch (c) { - case ' ': - // ignore space - break; - case '<': - daemon++; - paramType.append(c); - break; - case '>': - daemon--; - paramType.append(c); - break; - case ',': - if (daemon == 0) { - parameters.add(new JavaParameterInfo(paramName.toString(), paramType.toString())); - paramName.setLength(0); - paramType.setLength(0); - paramTypeParsing = false; - daemon = 0; - } else { + case ' ': + // ignore space + break; + case '<': + daemon++; + paramType.append(c); + break; + case '>': + daemon--; + paramType.append(c); + break; + case ',': + if (daemon == 0) { + parameters.add(new JavaParameterInfo(paramName.toString(), paramType.toString())); + paramName.setLength(0); + paramType.setLength(0); + paramTypeParsing = false; + daemon = 0; + } else { + paramType.append(c); + } + break; + default: paramType.append(c); - } - break; - default: - paramType.append(c); } } } @@ -448,7 +515,6 @@ public static JavaMethodInfo applyGenericTypeInvocation(JavaMethodInfo method, M JavaTypeInfo.applyGenericTypeInvocation(returnType, genericMap, newSignature); } newMethod.setSignature(newSignature.toString()); - newMethod.setGenericMember(method); return newMethod; } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/JavaTypeInfo.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/JavaTypeInfo.java index fa5806c8e..cc085bbe8 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/JavaTypeInfo.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/JavaTypeInfo.java @@ -142,6 +142,10 @@ public void setInvalidMethod(String methodName, InvalidMethodReason reason) { public void setInvalidMethods(Map invalidMethods) { this.invalidMethods = invalidMethods; } + + public Map getInvalidMethods() { + return invalidMethods; + } /** * Returns the java type parameters. diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/QuteResolvedJavaTypeParams.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/QuteResolvedJavaTypeParams.java index 0eb59109d..dfd919e43 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/QuteResolvedJavaTypeParams.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/QuteResolvedJavaTypeParams.java @@ -11,6 +11,8 @@ *******************************************************************************/ package com.redhat.qute.commons; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; + /** * Qute resolved java type parameters. * @@ -21,6 +23,8 @@ public class QuteResolvedJavaTypeParams { private String className; + private ValueResolverKind kind; + private String projectUri; public QuteResolvedJavaTypeParams() { @@ -28,7 +32,12 @@ public QuteResolvedJavaTypeParams() { } public QuteResolvedJavaTypeParams(String className, String projectUri) { + this(className, null, projectUri); + } + + public QuteResolvedJavaTypeParams(String className, ValueResolverKind kind, String projectUri) { setClassName(className); + setKind(kind); setProjectUri(projectUri); } @@ -48,4 +57,11 @@ public void setClassName(String className) { this.className = className; } + public ValueResolverKind getKind() { + return kind; + } + + public void setKind(ValueResolverKind kind) { + this.kind = kind; + } } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/datamodel/resolvers/ValueResolverKind.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/datamodel/resolvers/ValueResolverKind.java index a552c1835..869bbd83a 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/datamodel/resolvers/ValueResolverKind.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/datamodel/resolvers/ValueResolverKind.java @@ -17,12 +17,13 @@ * @author datho7561 */ public enum ValueResolverKind { - TemplateData(0), // - TemplateEnum(1), // - TemplateExtensionOnMethod(2), // - TemplateExtensionOnClass(3), // - TemplateGlobal(4), // - InjectedBean(5); + TemplateData(1), // + TemplateEnum(2), // + TemplateExtensionOnMethod(3), // + TemplateExtensionOnClass(4), // + TemplateGlobal(6), // + InjectedBean(6), // + Renarde(7); private final int value; diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsMethodKind.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsMethodKind.java new file mode 100644 index 000000000..9a76d46b0 --- /dev/null +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsMethodKind.java @@ -0,0 +1,41 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.commons.jaxrs; + +/** + * JaxRs method kind. + * + * @author Angelo ZERR + */ +public enum JaxRsMethodKind { + + POST(1), // + GET(2); // TODO : manage another kind like @DELETE, @PUT. it currently represents any jax ws-rs other than @POST + + private final int value; + + JaxRsMethodKind(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static JaxRsMethodKind forValue(int value) { + JaxRsMethodKind[] allValues = JaxRsMethodKind.values(); + if (value < 1 || value > allValues.length) + throw new IllegalArgumentException("Illegal enum value: " + value); + return allValues[value - 1]; + } + +} diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsParamKind.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsParamKind.java new file mode 100644 index 000000000..429ba0664 --- /dev/null +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/JaxRsParamKind.java @@ -0,0 +1,43 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.commons.jaxrs; + +/** + * JaxRs parameter kind. + * + * @author Angelo ZERR + */ +public enum JaxRsParamKind { + + NONE(0), // + QUERY(1), // + FORM(2), //s + PATH(3); + + private final int value; + + JaxRsParamKind(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static JaxRsParamKind forValue(int value) { + JaxRsParamKind[] allValues = JaxRsParamKind.values(); + if (value < 1 || value > allValues.length) + throw new IllegalArgumentException("Illegal enum value: " + value); + return allValues[value - 1]; + } + +} diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/RestParam.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/RestParam.java new file mode 100644 index 000000000..23ca659f7 --- /dev/null +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/commons/jaxrs/RestParam.java @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright (c) 2022 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.commons.jaxrs; + +/** + * Rest parameters informations. + * + * @author Angelo ZERR + * + */ +public class RestParam { + + private String name; + + private JaxRsParamKind parameterKind; + + private Boolean required; + + public RestParam(String name, JaxRsParamKind parameterKind, boolean required) { + this.name = name; + this.parameterKind = parameterKind; + this.required = required ? true : null; + } + + public String getName() { + return name; + } + + public JaxRsParamKind getParameterKind() { + return parameterKind; + } + + public boolean isRequired() { + return required != null ? required.booleanValue() : false; + } + +} diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/sections/CustomSection.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/sections/CustomSection.java index bc685e3f9..a9405537e 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/sections/CustomSection.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/sections/CustomSection.java @@ -44,6 +44,8 @@ public SectionKind getSectionKind() { protected void initializeParameters(List parameters) { // All parameters can have expression (ex : {#user name=order.item.parent // isActive=false age=10} + + // For user tag, one parameter can be an expression (for 'it') boolean hasIt = false; for (Parameter parameter : parameters) { if (parameter.hasValueAssigned()) { diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/QuteProjectRegistry.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/QuteProjectRegistry.java index 4978b5f52..18e624300 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/QuteProjectRegistry.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/QuteProjectRegistry.java @@ -48,6 +48,9 @@ import com.redhat.qute.commons.datamodel.JavaDataModelChangeEvent; import com.redhat.qute.commons.datamodel.QuteDataModelProjectParams; import com.redhat.qute.commons.datamodel.resolvers.NamespaceResolverInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; +import com.redhat.qute.commons.jaxrs.JaxRsParamKind; +import com.redhat.qute.commons.jaxrs.RestParam; import com.redhat.qute.commons.usertags.QuteUserTagParams; import com.redhat.qute.commons.usertags.UserTagInfo; import com.redhat.qute.ls.api.QuteDataModelProjectProvider; @@ -57,6 +60,7 @@ import com.redhat.qute.ls.api.QuteProjectInfoProvider; import com.redhat.qute.ls.api.QuteResolvedJavaTypeProvider; import com.redhat.qute.ls.api.QuteUserTagProvider; +import com.redhat.qute.parser.template.JavaTypeInfoProvider; import com.redhat.qute.parser.template.LiteralSupport; import com.redhat.qute.parser.template.Template; import com.redhat.qute.project.datamodel.ExtendedDataModelProject; @@ -69,7 +73,6 @@ import com.redhat.qute.services.nativemode.JavaTypeFilter; import com.redhat.qute.services.nativemode.ReflectionJavaTypeFilter; import com.redhat.qute.settings.QuteNativeSettings; -import com.redhat.qute.settings.SharedSettings; import com.redhat.qute.utils.StringUtils; /** @@ -218,7 +221,8 @@ public void onDidCloseTextDocument(TemplateInfoProvider document) { } } - public CompletableFuture resolveJavaType(String javaTypeName, String projectUri) { + public CompletableFuture resolveJavaType(String javaTypeName, + JavaTypeInfoProvider javaTypeInfo, String projectUri) { QuteProject project = StringUtils.isEmpty(projectUri) ? null : getProject(projectUri); if (project == null) { return RESOLVED_JAVA_CLASSINFO_NULL_FUTURE; @@ -227,10 +231,11 @@ public CompletableFuture resolveJavaType(String javaTypeNa if (future != null) { return future; } - return resolveJavaType(javaTypeName, project, new HashSet<>()); + return resolveJavaType(javaTypeName, javaTypeInfo, project, new HashSet<>()); } - private CompletableFuture resolveJavaType(String javaTypeName, QuteProject project, + private CompletableFuture resolveJavaType(String javaTypeName, + JavaTypeInfoProvider javaTypeInfo, QuteProject project, Set visited) { if (StringUtils.isEmpty(javaTypeName)) { @@ -255,7 +260,7 @@ private CompletableFuture resolveJavaType(String javaTypeN if (javaTypeName.endsWith("[]")) { // Array case (ex : org.acme.Item[]), try to to get the, get the item type (ex : // org.acme.Item) - future = resolveJavaType(javaTypeName.substring(0, javaTypeName.length() - 2), projectUri) // + future = resolveJavaType(javaTypeName.substring(0, javaTypeName.length() - 2), javaTypeInfo, projectUri) // .thenApply(item -> { ResolvedJavaTypeInfo array = new ResolvedJavaTypeInfo(); array.setSignature(javaTypeName); @@ -267,7 +272,7 @@ private CompletableFuture resolveJavaType(String javaTypeN // - java.util.List -> java.util.List // - java.lang.String -> java.lang.String CompletableFuture loadResolveJavaTypeFuture = resolveJavaTypeWithoutGeneric( - javaTypeName, project); + javaTypeName, javaTypeInfo, project); future = loadResolveJavaTypeFuture // .thenCompose(resolvedJavaType -> { if (resolvedJavaType != null) { @@ -284,7 +289,8 @@ private CompletableFuture resolveJavaType(String javaTypeN if (resolvedJavaType.getExtendedTypes() != null) { Set> resolvingExtendedFutures = new HashSet<>(); for (String extendedType : resolvedJavaType.getExtendedTypes()) { - resolvingExtendedFutures.add(resolveJavaType(extendedType, project, visited)); + resolvingExtendedFutures + .add(resolveJavaType(extendedType, javaTypeInfo, project, visited)); } if (!resolvingExtendedFutures.isEmpty()) { CompletableFuture allFutures = CompletableFuture @@ -354,6 +360,7 @@ public void updateIterable(final ResolvedJavaTypeInfo resolvedJavaType, } private CompletableFuture resolveJavaTypeWithoutGeneric(String javaTypeName, + JavaTypeInfoProvider javaTypeInfo, QuteProject project) { String javaTypeWithoutGeneric = javaTypeName; int genericIndex = javaTypeName.indexOf('<'); @@ -370,7 +377,11 @@ private CompletableFuture resolveJavaTypeWithoutGeneric(St } // The Java type (without generic) is not loaded from JDT / IJ side, load it. String projectUri = project.getUri(); - QuteResolvedJavaTypeParams params = new QuteResolvedJavaTypeParams(javaTypeWithoutGeneric, projectUri); + ValueResolverKind kind = null; + if (javaTypeInfo != null && javaTypeInfo instanceof ValueResolver) { + kind = ((ValueResolver) javaTypeInfo).getKind(); + } + QuteResolvedJavaTypeParams params = new QuteResolvedJavaTypeParams(javaTypeWithoutGeneric, kind, projectUri); return getResolvedJavaType(params); } @@ -580,7 +591,7 @@ private JavaMemberInfo findPropertyWithJavaReflection(ResolvedJavaTypeInfo baseT if (baseType.getExtendedTypes() != null) { // Search in extended types for (String superType : baseType.getExtendedTypes()) { - ResolvedJavaTypeInfo resolvedSuperType = resolveJavaType(superType, projectUri).getNow(null); + ResolvedJavaTypeInfo resolvedSuperType = resolveJavaType(superType, null, projectUri).getNow(null); if (resolvedSuperType != null) { JavaMemberInfo superMemberInfo = findPropertyWithJavaReflection(resolvedSuperType, property, projectUri, visited); @@ -748,7 +759,7 @@ private boolean findMethod(ResolvedJavaTypeInfo baseType, String methodName, if (baseType.getExtendedTypes() != null) { // Search in extended types for (String superType : baseType.getExtendedTypes()) { - ResolvedJavaTypeInfo resolvedSuperType = resolveJavaType(superType, projectUri).getNow(null); + ResolvedJavaTypeInfo resolvedSuperType = resolveJavaType(superType, null, projectUri).getNow(null); if (resolvedSuperType != null) { if (findMethod(resolvedSuperType, methodName, parameterTypes, result, projectUri, visited)) { return true; @@ -826,7 +837,40 @@ private boolean isMatchParameters(JavaMethodInfo method, List nbRequiredParameters + nbOptionalParameters) { + return false; + } + nbParameters = declaredNbParameters; + valid = true; + } + if (!valid) { + return false; + } } // Validate all mandatory parameters (without varargs) @@ -1211,7 +1255,7 @@ private boolean isMatchType(ResolvedJavaTypeInfo javaType, String parameterType, } // Loop for other levels of super types (ex SmallItem) - ResolvedJavaTypeInfo resolvedSuperType = resolveJavaType(superType, projectUri).getNow(null); + ResolvedJavaTypeInfo resolvedSuperType = resolveJavaType(superType, null, projectUri).getNow(null); if (resolvedSuperType != null) { if (isMatchType(resolvedSuperType, parameterType, projectUri, visited)) { return true; @@ -1220,7 +1264,7 @@ private boolean isMatchType(ResolvedJavaTypeInfo javaType, String parameterType, } } if (!javaType.getTypeParameters().isEmpty()) { - ResolvedJavaTypeInfo result = resolveJavaType(resolvedTypeName, projectUri).getNow(null); + ResolvedJavaTypeInfo result = resolveJavaType(resolvedTypeName, null, projectUri).getNow(null); if (result != null && result.getExtendedTypes() != null) { for (String superType : result.getExtendedTypes()) { if (isSameType(parameterType, superType)) { diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/ExtendedDataModelProject.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/ExtendedDataModelProject.java index 3e5b82438..65456db4f 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/ExtendedDataModelProject.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/ExtendedDataModelProject.java @@ -88,6 +88,7 @@ private static void updateValueResolvers(List typeValueResolv typeValueResolver.setSignature(resolver.getSignature()); typeValueResolver.setSourceType(resolver.getSourceType()); typeValueResolver.setGlobalVariable(resolver.isGlobalVariable()); + typeValueResolver.setKind(resolver.getKind()); typeValueResolvers.add(typeValueResolver); break; case FIELD: @@ -97,6 +98,7 @@ private static void updateValueResolvers(List typeValueResolv fieldValueResolver.setSignature(resolver.getSignature()); fieldValueResolver.setSourceType(resolver.getSourceType()); fieldValueResolver.setGlobalVariable(resolver.isGlobalVariable()); + fieldValueResolver.setKind(resolver.getKind()); fieldValueResolvers.add(fieldValueResolver); break; case METHOD: @@ -107,6 +109,7 @@ private static void updateValueResolvers(List typeValueResolv methodValueResolver.setSignature(resolver.getSignature()); methodValueResolver.setSourceType(resolver.getSourceType()); methodValueResolver.setGlobalVariable(resolver.isGlobalVariable()); + methodValueResolver.setKind(resolver.getKind()); methodValueResolvers.add(methodValueResolver); break; default: diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/JavaDataModelCache.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/JavaDataModelCache.java index 06394f7d2..cb48823eb 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/JavaDataModelCache.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/JavaDataModelCache.java @@ -73,8 +73,14 @@ public List getGlobalVariables(String projectUri) { return projectRegistry.getGlobalVariables(projectUri); } - public CompletableFuture resolveJavaType(String className, String projectUri) { - return projectRegistry.resolveJavaType(className, projectUri); + public CompletableFuture resolveJavaType(String className, + String projectUri) { + return resolveJavaType(className, null, projectUri); + } + + public CompletableFuture resolveJavaType(String className, JavaTypeInfoProvider javaTypeInfo, + String projectUri) { + return projectRegistry.resolveJavaType(className, javaTypeInfo, projectUri); } public CompletableFuture resolveJavaType(Parameter parameter, String projectUri) { @@ -100,28 +106,28 @@ private CompletableFuture resolveJavaType(Parts parts, int for (int i = 0; i < partIndex + 1; i++) { Part current = (parts.getChild(i)); switch (current.getPartKind()) { - case Object: - ObjectPart objectPart = (ObjectPart) current; - future = resolveJavaType(objectPart, projectUri, nullIfDontMatchWithIterable); - break; - case Property: - case Method: - if (future != null) { - ResolvedJavaTypeInfo actualResolvedType = future.getNow(null); - if (actualResolvedType != null) { - future = resolveJavaType(current, projectUri, actualResolvedType); - } else { - future = future // - .thenCompose(resolvedType -> { - if (resolvedType == null) { - return RESOLVED_JAVA_TYPE_INFO_NULL_FUTURE; - } - return resolveJavaType(current, projectUri, resolvedType); - }); + case Object: + ObjectPart objectPart = (ObjectPart) current; + future = resolveJavaType(objectPart, projectUri, nullIfDontMatchWithIterable); + break; + case Property: + case Method: + if (future != null) { + ResolvedJavaTypeInfo actualResolvedType = future.getNow(null); + if (actualResolvedType != null) { + future = resolveJavaType(current, projectUri, actualResolvedType); + } else { + future = future // + .thenCompose(resolvedType -> { + if (resolvedType == null) { + return RESOLVED_JAVA_TYPE_INFO_NULL_FUTURE; + } + return resolveJavaType(current, projectUri, resolvedType); + }); + } } - } - break; - default: + break; + default: } } return future != null ? future : RESOLVED_JAVA_TYPE_INFO_NULL_FUTURE; @@ -173,7 +179,7 @@ private CompletableFuture resolveJavaType(ObjectPart objec } if (future == null) { - future = resolveJavaType(javaType, projectUri); + future = resolveJavaType(javaType, javaTypeInfo, projectUri); } Node node = javaTypeInfo.getJavaTypeOwnerNode(); Section section = getOwnerSection(node); @@ -360,7 +366,8 @@ public CompletableFuture getJavadoc(JavaMemberInfo javaMemberInfo, JavaT String projectUri, boolean hasMarkdown) { String typeName = javaMemberInfo.getJavaTypeInfo() != null ? javaMemberInfo.getJavaTypeInfo().getName() : javaTypeInfo.getName(); - String signature = javaMemberInfo.getGenericMember() == null ? javaMemberInfo.getSignature() : javaMemberInfo.getGenericMember().getSignature(); + String signature = javaMemberInfo.getGenericMember() == null ? javaMemberInfo.getSignature() + : javaMemberInfo.getGenericMember().getSignature(); return projectRegistry.getJavadoc(new QuteJavadocParams(typeName, projectUri, javaMemberInfo.getName(), signature, hasMarkdown ? DocumentFormat.Markdown : DocumentFormat.PlainText)); } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/FieldValueResolver.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/FieldValueResolver.java index 875906bb0..60231f2f4 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/FieldValueResolver.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/FieldValueResolver.java @@ -15,6 +15,7 @@ import com.redhat.qute.commons.JavaFieldInfo; import com.redhat.qute.commons.JavaTypeInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; import com.redhat.qute.parser.template.JavaTypeInfoProvider; import com.redhat.qute.parser.template.Node; @@ -46,6 +47,8 @@ public class FieldValueResolver extends JavaFieldInfo implements ValueResolver, private Boolean globalVariable; + private ValueResolverKind kind; + @Override public String getNamespace() { return namespace; @@ -112,6 +115,15 @@ public String getJavaType() { return getJavaElementType(); } + @Override + public ValueResolverKind getKind() { + return kind; + } + + public void setKind(ValueResolverKind kind) { + this.kind = kind; + } + @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this); @@ -120,6 +132,7 @@ public String toString() { b.add("signature", this.getSignature()); b.add("sourceType", this.getSourceType()); b.add("globalVariable", this.isGlobalVariable()); + b.add("kind", this.getKind()); return b.toString(); } } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/MethodValueResolver.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/MethodValueResolver.java index 1ece61053..b200581be 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/MethodValueResolver.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/MethodValueResolver.java @@ -19,6 +19,7 @@ import com.redhat.qute.commons.JavaParameterInfo; import com.redhat.qute.commons.JavaTypeInfo; import com.redhat.qute.commons.ResolvedJavaTypeInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; import com.redhat.qute.parser.template.DocumentableItem; import com.redhat.qute.parser.template.JavaTypeInfoProvider; import com.redhat.qute.parser.template.Node; @@ -29,7 +30,8 @@ * @author Angelo ZERR * */ -public class MethodValueResolver extends JavaMethodInfo implements ValueResolver, JavaTypeInfoProvider, DocumentableItem { +public class MethodValueResolver extends JavaMethodInfo + implements ValueResolver, JavaTypeInfoProvider, DocumentableItem { private String named; @@ -44,9 +46,11 @@ public class MethodValueResolver extends JavaMethodInfo implements ValueResolver private List sample; private String url; - + private Boolean globalVariable; + private ValueResolverKind kind; + @Override public boolean isVirtual() { return namespace == null; @@ -207,11 +211,11 @@ public boolean isValidName() { String name = getName(); return name.length() > 0 && name.charAt(0) != '@'; } - + public boolean isGlobalVariable() { return globalVariable != null && globalVariable.booleanValue(); } - + public void setGlobalVariable(boolean globalVariable) { this.globalVariable = globalVariable; } @@ -226,6 +230,15 @@ public String getJavaType() { return getJavaElementType(); } + @Override + public ValueResolverKind getKind() { + return kind; + } + + public void setKind(ValueResolverKind kind) { + this.kind = kind; + } + @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this); @@ -237,6 +250,7 @@ public String toString() { b.add("sample", this.getSample()); b.add("url", this.getUrl()); b.add("globalVariable", this.isGlobalVariable()); + b.add("kind", this.getKind()); return b.toString(); } } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/TypeValueResolver.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/TypeValueResolver.java index 7f512653f..383fad266 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/TypeValueResolver.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/TypeValueResolver.java @@ -14,6 +14,7 @@ import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; import com.redhat.qute.commons.JavaTypeInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; import com.redhat.qute.parser.template.JavaTypeInfoProvider; import com.redhat.qute.parser.template.Node; @@ -45,6 +46,8 @@ public class TypeValueResolver extends JavaTypeInfo implements ValueResolver, Ja private Boolean globalVariable; + private ValueResolverKind kind; + @Override public String getNamespace() { return namespace; @@ -105,6 +108,14 @@ public String getJavaType() { return getJavaElementType(); } + @Override + public ValueResolverKind getKind() { + return kind; + } + public void setKind(ValueResolverKind kind) { + this.kind = kind; + } + @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this); @@ -113,6 +124,7 @@ public String toString() { b.add("signature", this.getSignature()); b.add("sourceType", this.getSourceType()); b.add("globalVariable", this.isGlobalVariable()); + b.add("kind", this.getKind()); return b.toString(); } } \ No newline at end of file diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/ValueResolver.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/ValueResolver.java index f13e31951..ed230d4f3 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/ValueResolver.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/datamodel/resolvers/ValueResolver.java @@ -12,6 +12,7 @@ package com.redhat.qute.project.datamodel.resolvers; import com.redhat.qute.commons.JavaElementKind; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; /** * Value resolver API. @@ -79,4 +80,5 @@ public interface ValueResolver { */ boolean isGlobalVariable(); + ValueResolverKind getKind(); } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/QuteDiagnostics.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/QuteDiagnostics.java index 5b18d3b1e..5a4beec8f 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/QuteDiagnostics.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/QuteDiagnostics.java @@ -1022,6 +1022,9 @@ private ResolvedJavaTypeInfo validateMethodPart(MethodPart methodPart, Section o return null; } + if (method.isVoidMethod()) { + return null; + } String memberType = method.resolveJavaElementType(iterableOfType); return validateJavaTypePart(methodPart, ownerSection, projectUri, diagnostics, resolvingJavaTypeContext, memberType, null); diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/completions/QuteCompletionsForExpression.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/completions/QuteCompletionsForExpression.java index 58cee94b2..08b921e2d 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/completions/QuteCompletionsForExpression.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/completions/QuteCompletionsForExpression.java @@ -36,6 +36,8 @@ import com.redhat.qute.commons.JavaMethodInfo; import com.redhat.qute.commons.JavaParameterInfo; import com.redhat.qute.commons.ResolvedJavaTypeInfo; +import com.redhat.qute.commons.jaxrs.JaxRsParamKind; +import com.redhat.qute.commons.jaxrs.RestParam; import com.redhat.qute.ls.commons.snippets.SnippetsBuilder; import com.redhat.qute.parser.expression.MethodPart; import com.redhat.qute.parser.expression.NamespacePart; @@ -128,33 +130,34 @@ public CompletableFuture doCompleteExpression(CompletionRequest if (nodeExpression.getKind() == NodeKind.ExpressionPart) { Part part = (Part) nodeExpression; switch (part.getPartKind()) { - case Object: - // ex : { ite|m } - return doCompleteExpressionForObjectPart(null, expression, part.getNamespace(), part, offset, template, - completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); - case Property: { - // ex : { item.n| } - // ex : { item.n|ame } - Parts parts = part.getParent(); - return doCompleteExpressionForMemberPart(part, parts, template, false, completionSettings, - formattingSettings, nativeImagesSettings, cancelChecker); - } - case Method: { - // ex : { item.getN|ame() } - // ex : { item.getName(|) } - MethodPart methodPart = (MethodPart) part; - if (methodPart.isInParameters(offset)) { + case Object: + // ex : { ite|m } + return doCompleteExpressionForObjectPart(null, expression, part.getNamespace(), part, offset, + template, + completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); + case Property: { + // ex : { item.n| } + // ex : { item.n|ame } + Parts parts = part.getParent(); + return doCompleteExpressionForMemberPart(part, parts, template, false, completionSettings, + formattingSettings, nativeImagesSettings, cancelChecker); + } + case Method: { + // ex : { item.getN|ame() } // ex : { item.getName(|) } - return doCompleteExpressionForObjectPart(null, expression, null, null, offset, template, + MethodPart methodPart = (MethodPart) part; + if (methodPart.isInParameters(offset)) { + // ex : { item.getName(|) } + return doCompleteExpressionForObjectPart(null, expression, null, null, offset, template, + completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); + } + // ex : { item.getN|ame() } + Parts parts = part.getParent(); + return doCompleteExpressionForMemberPart(part, parts, template, methodPart.isInfixNotation(), completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); } - // ex : { item.getN|ame() } - Parts parts = part.getParent(); - return doCompleteExpressionForMemberPart(part, parts, template, methodPart.isInfixNotation(), - completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); - } - default: - break; + default: + break; } return EMPTY_FUTURE_COMPLETION; } @@ -162,42 +165,43 @@ public CompletableFuture doCompleteExpression(CompletionRequest if (nodeExpression.getKind() == NodeKind.ExpressionParts) { char previous = template.getText().charAt(offset - 1); switch (previous) { - case ':': { - // ex : { data:| } - // ex : { data:|name } - Parts parts = (Parts) nodeExpression; - Part part = parts.getPartAt(offset + 1); - return doCompleteExpressionForObjectPart(null, expression, parts.getNamespace(), part, offset, template, - completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); - } - case '.': { - // ex : { item.| } - // ex : { item.|name } - // ex : { item.|getName() } - Parts parts = (Parts) nodeExpression; - Part part = parts.getPartAt(offset + 1); - return doCompleteExpressionForMemberPart(part, parts, template, false, completionSettings, - formattingSettings, nativeImagesSettings, cancelChecker); - } - case ' ': { - // Infix notation - // ex : { item | } - // ex : { item |name } - // ex : { item ?: |name } - Parts parts = (Parts) nodeExpression; - Part part = parts.getPartAt(offset + 1); - Part previousPart = parts.getPreviousPart(part); - if (previousPart != null && previousPart.getPartKind() == PartKind.Method - && ((MethodPart) previousPart).isOperator()) { - // ex : { item ?: |name } + case ':': { + // ex : { data:| } + // ex : { data:|name } + Parts parts = (Parts) nodeExpression; + Part part = parts.getPartAt(offset + 1); return doCompleteExpressionForObjectPart(null, expression, parts.getNamespace(), part, offset, - template, completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); + template, + completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); + } + case '.': { + // ex : { item.| } + // ex : { item.|name } + // ex : { item.|getName() } + Parts parts = (Parts) nodeExpression; + Part part = parts.getPartAt(offset + 1); + return doCompleteExpressionForMemberPart(part, parts, template, false, completionSettings, + formattingSettings, nativeImagesSettings, cancelChecker); + } + case ' ': { + // Infix notation + // ex : { item | } + // ex : { item |name } + // ex : { item ?: |name } + Parts parts = (Parts) nodeExpression; + Part part = parts.getPartAt(offset + 1); + Part previousPart = parts.getPreviousPart(part); + if (previousPart != null && previousPart.getPartKind() == PartKind.Method + && ((MethodPart) previousPart).isOperator()) { + // ex : { item ?: |name } + return doCompleteExpressionForObjectPart(null, expression, parts.getNamespace(), part, offset, + template, completionSettings, formattingSettings, nativeImagesSettings, cancelChecker); + } + // ex : { item | } + // ex : { item |name } + return doCompleteExpressionForMemberPart(part, parts, template, true, completionSettings, + formattingSettings, nativeImagesSettings, cancelChecker); } - // ex : { item | } - // ex : { item |name } - return doCompleteExpressionForMemberPart(part, parts, template, true, completionSettings, - formattingSettings, nativeImagesSettings, cancelChecker); - } } } return EMPTY_FUTURE_COMPLETION; @@ -599,7 +603,8 @@ private static String createMethodSnippet(JavaMethodInfo method, boolean infixNo } else { snippet.append(' '); } - for (int i = start; i < method.getParameters().size(); i++) { + int end = getRequiredParametersSize(method); + for (int i = start; i < end; i++) { if (i > start) { snippet.append(", "); } @@ -621,6 +626,26 @@ private static String createMethodSnippet(JavaMethodInfo method, boolean infixNo return snippet.toString(); } + private static int getRequiredParametersSize(JavaMethodInfo method) { + int nbParameters = method.getParameters().size(); + if (method.getJaxRsMethodKind() != null) { + // Renarde parameters, only @ResPath (required parameters) must be shown in the + // completion + int nbRequiredParameters = 0; + for (int i = 0; i < nbParameters; i++) { + JavaParameterInfo parameterInfo = method.getParameters().get(i); + RestParam restParam = method.getRestParameter(parameterInfo.getName()); + if (restParam != null) { + if (restParam.getParameterKind() == JaxRsParamKind.PATH) { + nbRequiredParameters++; + } + } + } + return nbRequiredParameters; + } + return nbParameters; + } + private CompletableFuture doCompleteExpressionForObjectPart(CompletionRequest completionRequest, Expression expression, String namespace, Node part, int offset, Template template, QuteCompletionSettings completionSettings, QuteFormattingSettings formattingSettings, @@ -718,14 +743,14 @@ private void doCompleteExpressionForObjectPartWithGlobalVariables(Template templ private static CompletionItemKind getCompletionKind(ValueResolver globalVariable) { switch (globalVariable.getJavaElementKind()) { - case FIELD: - return CompletionItemKind.Field; - case METHOD: - return CompletionItemKind.Method; - case TYPE: - return CompletionItemKind.Class; - case PARAMETER: - return CompletionItemKind.TypeParameter; + case FIELD: + return CompletionItemKind.Field; + case METHOD: + return CompletionItemKind.Method; + case TYPE: + return CompletionItemKind.Class; + case PARAMETER: + return CompletionItemKind.TypeParameter; } return CompletionItemKind.Class; } @@ -770,27 +795,28 @@ public void doCompleteNamespaceResolvers(String namespace, Template template, Ra } } else { switch (resolver.getJavaElementKind()) { - case METHOD: { - MethodValueResolver method = (MethodValueResolver) resolver; - CompletionItem item = fillCompletionMethod(method, method.getNamespace(), useNamespaceInTextEdit, - range, false, completionSettings, formattingSettings, completionItems); - item.setKind(CompletionItemKind.Function); - // Display namespace resolvers (ex : config:getConfigProperty(...)) after - // declared objects - item.setSortText("Zc" + item.getLabel()); - break; - } - case FIELD: { - FieldValueResolver field = (FieldValueResolver) resolver; - CompletionItem item = fillCompletionField(field, field.getNamespace(), namespace == null, range, - completionItems); - item.setKind(CompletionItemKind.Field); - // Display namespace resolvers (ex : inject:bean) after - // declared objects - item.setSortText("Zb" + item.getLabel()); - break; - } - default: + case METHOD: { + MethodValueResolver method = (MethodValueResolver) resolver; + CompletionItem item = fillCompletionMethod(method, method.getNamespace(), + useNamespaceInTextEdit, + range, false, completionSettings, formattingSettings, completionItems); + item.setKind(CompletionItemKind.Function); + // Display namespace resolvers (ex : config:getConfigProperty(...)) after + // declared objects + item.setSortText("Zc" + item.getLabel()); + break; + } + case FIELD: { + FieldValueResolver field = (FieldValueResolver) resolver; + CompletionItem item = fillCompletionField(field, field.getNamespace(), namespace == null, range, + completionItems); + item.setKind(CompletionItemKind.Field); + // Display namespace resolvers (ex : inject:bean) after + // declared objects + item.setSortText("Zb" + item.getLabel()); + break; + } + default: } } } @@ -838,50 +864,29 @@ private CompletableFuture doCompleteExpressionForObjectPartWithParentNodes // 2) Completion for aliases section switch (parentSection.getSectionKind()) { - case EACH: - case FOR: - LoopSection iterableSection = ((LoopSection) parentSection); - // Completion for iterable section like #each, #for - String alias = iterableSection.getAlias(); - if (!StringUtils.isEmpty(alias)) { - if (!existingVars.contains(alias)) { - existingVars.add(alias); - CompletionItem item = new CompletionItem(); - item.setLabel(alias); - item.setKind(CompletionItemKind.Reference); - TextEdit textEdit = new TextEdit(range, alias); - item.setTextEdit(Either.forLeft(textEdit)); - completionItems.add(item); - } - } - break; - case LET: - case SET: { - // completion for parameters coming from #let, #set - List parameters = parentSection.getParameters(); - if (parameters != null) { - for (Parameter parameter : parameters) { - String parameterName = parameter.getName(); - if (!existingVars.contains(parameterName)) { - existingVars.add(parameterName); + case EACH: + case FOR: + LoopSection iterableSection = ((LoopSection) parentSection); + // Completion for iterable section like #each, #for + String alias = iterableSection.getAlias(); + if (!StringUtils.isEmpty(alias)) { + if (!existingVars.contains(alias)) { + existingVars.add(alias); CompletionItem item = new CompletionItem(); - item.setLabel(parameterName); + item.setLabel(alias); item.setKind(CompletionItemKind.Reference); - TextEdit textEdit = new TextEdit(range, parameterName); + TextEdit textEdit = new TextEdit(range, alias); item.setTextEdit(Either.forLeft(textEdit)); completionItems.add(item); } } - } - break; - } - case IF: { - // completion for parameters coming from #if - List parameters = parentSection.getParameters(); - if (parameters != null) { - for (Parameter parameter : parameters) { - if (parameter.isOptional()) { - // {#if foo??} + break; + case LET: + case SET: { + // completion for parameters coming from #let, #set + List parameters = parentSection.getParameters(); + if (parameters != null) { + for (Parameter parameter : parameters) { String parameterName = parameter.getName(); if (!existingVars.contains(parameterName)) { existingVars.add(parameterName); @@ -894,84 +899,110 @@ private CompletableFuture doCompleteExpressionForObjectPartWithParentNodes } } } + break; } - break; - } - case WITH: - // Completion for properties/methods of with object from #with - Parameter object = ((WithSection) parentSection).getObjectParameter(); - if (object != null) { - String projectUri = template.getProjectUri(); - ResolvedJavaTypeInfo withJavaTypeInfo = javaCache.resolveJavaType(object, projectUri) - .getNow(null); - if (withJavaTypeInfo != null) { - JavaTypeFilter filter = javaCache.getJavaTypeFilter(projectUri, nativeImagesSettings); - JavaTypeAccessibiltyRule javaTypeAccessibility = filter.getJavaTypeAccessibility( - withJavaTypeInfo, template.getJavaTypesSupportedInNativeMode()); - fillCompletionFields(withJavaTypeInfo, javaTypeAccessibility, filter, range, projectUri, - existingVars, completionItems); - fillCompletionMethods(withJavaTypeInfo, javaTypeAccessibility, filter, range, projectUri, - false, completionSettings, formattingSettings, existingVars, new HashSet<>(), - completionItems); + case IF: { + // completion for parameters coming from #if + List parameters = parentSection.getParameters(); + if (parameters != null) { + for (Parameter parameter : parameters) { + if (parameter.isOptional()) { + // {#if foo??} + String parameterName = parameter.getName(); + if (!existingVars.contains(parameterName)) { + existingVars.add(parameterName); + CompletionItem item = new CompletionItem(); + item.setLabel(parameterName); + item.setKind(CompletionItemKind.Reference); + TextEdit textEdit = new TextEdit(range, parameterName); + item.setTextEdit(Either.forLeft(textEdit)); + completionItems.add(item); + } + } + } } + break; } - break; - case WHEN: - case SWITCH: - if (node.getKind() == NodeKind.Expression) { - Expression expression = (Expression) node; - if (Section.isCaseSection(expression.getOwnerSection())) { - // {#case | ...} - Parameter triggeredParameter = expression.getOwnerParameter(); - CaseSection caseSection = (CaseSection) expression.getOwnerSection(); - // Completion for properties/methods of with object from #switch and #when - Parameter value = ((WhenSection) parentSection).getValueParameter(); - if (value != null) { - String projectUri = template.getProjectUri(); - ResolvedJavaTypeInfo whenJavaType = javaCache.resolveJavaType(value, projectUri) - .getNow(null); - if (whenJavaType != null && whenJavaType.isEnum()) { - JavaTypeFilter filter = javaCache.getJavaTypeFilter(projectUri, - nativeImagesSettings); - JavaTypeAccessibiltyRule javaTypeAccessibility = filter.getJavaTypeAccessibility( - whenJavaType, template.getJavaTypesSupportedInNativeMode()); - CompletionCaseResult result = caseSection.getCompletionCaseResultAt(offset, - triggeredParameter); - // Add existing variables - Set caseExistingVars = new HashSet(existingVars); - for (Parameter parameter : caseSection.getParameters()) { - caseExistingVars.add(parameter.getName()); - } - boolean canSupportMarkdown = completionSettings - .canSupportMarkupKind(MarkupKind.MARKDOWN); - // Complete operator and/or field according to result - switch (result) { - case ALL_OPERATOR_AND_FIELD: - fillCaseOperators(caseSection, false, range, caseExistingVars, completionItems, - canSupportMarkdown); - fillCompletionFields(whenJavaType, javaTypeAccessibility, filter, range, - projectUri, caseExistingVars, completionItems); - break; - case ALL_OPERATOR: - fillCaseOperators(caseSection, false, range, caseExistingVars, completionItems, - canSupportMarkdown); - break; - case FIELD_ONLY: - fillCompletionFields(whenJavaType, javaTypeAccessibility, filter, range, - projectUri, caseExistingVars, completionItems); - break; - case MULTI_OPERATOR_ONLY: - fillCaseOperators(caseSection, true, range, caseExistingVars, completionItems, - canSupportMarkdown); - break; - case NONE: - break; + case WITH: + // Completion for properties/methods of with object from #with + Parameter object = ((WithSection) parentSection).getObjectParameter(); + if (object != null) { + String projectUri = template.getProjectUri(); + ResolvedJavaTypeInfo withJavaTypeInfo = javaCache.resolveJavaType(object, projectUri) + .getNow(null); + if (withJavaTypeInfo != null) { + JavaTypeFilter filter = javaCache.getJavaTypeFilter(projectUri, nativeImagesSettings); + JavaTypeAccessibiltyRule javaTypeAccessibility = filter.getJavaTypeAccessibility( + withJavaTypeInfo, template.getJavaTypesSupportedInNativeMode()); + fillCompletionFields(withJavaTypeInfo, javaTypeAccessibility, filter, range, projectUri, + existingVars, completionItems); + fillCompletionMethods(withJavaTypeInfo, javaTypeAccessibility, filter, range, + projectUri, + false, completionSettings, formattingSettings, existingVars, new HashSet<>(), + completionItems); + } + } + break; + case WHEN: + case SWITCH: + if (node.getKind() == NodeKind.Expression) { + Expression expression = (Expression) node; + if (Section.isCaseSection(expression.getOwnerSection())) { + // {#case | ...} + Parameter triggeredParameter = expression.getOwnerParameter(); + CaseSection caseSection = (CaseSection) expression.getOwnerSection(); + // Completion for properties/methods of with object from #switch and #when + Parameter value = ((WhenSection) parentSection).getValueParameter(); + if (value != null) { + String projectUri = template.getProjectUri(); + ResolvedJavaTypeInfo whenJavaType = javaCache.resolveJavaType(value, projectUri) + .getNow(null); + if (whenJavaType != null && whenJavaType.isEnum()) { + JavaTypeFilter filter = javaCache.getJavaTypeFilter(projectUri, + nativeImagesSettings); + JavaTypeAccessibiltyRule javaTypeAccessibility = filter + .getJavaTypeAccessibility( + whenJavaType, template.getJavaTypesSupportedInNativeMode()); + CompletionCaseResult result = caseSection.getCompletionCaseResultAt(offset, + triggeredParameter); + // Add existing variables + Set caseExistingVars = new HashSet(existingVars); + for (Parameter parameter : caseSection.getParameters()) { + caseExistingVars.add(parameter.getName()); + } + boolean canSupportMarkdown = completionSettings + .canSupportMarkupKind(MarkupKind.MARKDOWN); + // Complete operator and/or field according to result + switch (result) { + case ALL_OPERATOR_AND_FIELD: + fillCaseOperators(caseSection, false, range, caseExistingVars, + completionItems, + canSupportMarkdown); + fillCompletionFields(whenJavaType, javaTypeAccessibility, filter, range, + projectUri, caseExistingVars, completionItems); + break; + case ALL_OPERATOR: + fillCaseOperators(caseSection, false, range, caseExistingVars, + completionItems, + canSupportMarkdown); + break; + case FIELD_ONLY: + fillCompletionFields(whenJavaType, javaTypeAccessibility, filter, range, + projectUri, caseExistingVars, completionItems); + break; + case MULTI_OPERATOR_ONLY: + fillCaseOperators(caseSection, true, range, caseExistingVars, + completionItems, + canSupportMarkdown); + break; + case NONE: + break; + } } } } } - } - default: + default: } } } diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/project/MockQuteProject.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/project/MockQuteProject.java index 4372eec5e..13c5b469e 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/project/MockQuteProject.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/project/MockQuteProject.java @@ -87,18 +87,18 @@ public List getJavaTypes() { return new ArrayList<>(fromResolved); } - protected static JavaMemberInfo registerField(String fieldSignature, ResolvedJavaTypeInfo resolvedType) { - JavaFieldInfo member = new JavaFieldInfo(); - member.setSignature(fieldSignature); - resolvedType.getFields().add(member); - return member; + protected static JavaFieldInfo registerField(String fieldSignature, ResolvedJavaTypeInfo resolvedType) { + JavaFieldInfo field = new JavaFieldInfo(); + field.setSignature(fieldSignature); + resolvedType.getFields().add(field); + return field; } - protected static JavaMemberInfo registerMethod(String methodSignature, ResolvedJavaTypeInfo resolvedType) { - JavaMethodInfo member = new JavaMethodInfo(); - member.setSignature(methodSignature); - resolvedType.getMethods().add(member); - return member; + protected static JavaMethodInfo registerMethod(String methodSignature, ResolvedJavaTypeInfo resolvedType) { + JavaMethodInfo method = new JavaMethodInfo(); + method.setSignature(methodSignature); + resolvedType.getMethods().add(method); + return method; } protected static JavaTypeInfo createJavaTypeInfo(String typeName, JavaTypeKind kind, List cache) { diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/project/QuteQuickStartProject.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/project/QuteQuickStartProject.java index 0c1f7d326..f07002a77 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/project/QuteQuickStartProject.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/project/QuteQuickStartProject.java @@ -19,6 +19,7 @@ import com.redhat.qute.commons.InvalidMethodReason; import com.redhat.qute.commons.JavaMemberInfo; +import com.redhat.qute.commons.JavaMethodInfo; import com.redhat.qute.commons.JavaTypeInfo; import com.redhat.qute.commons.JavaTypeKind; import com.redhat.qute.commons.ProjectInfo; @@ -31,6 +32,9 @@ import com.redhat.qute.commons.datamodel.resolvers.NamespaceResolverInfo; import com.redhat.qute.commons.datamodel.resolvers.ValueResolverInfo; import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; +import com.redhat.qute.commons.jaxrs.JaxRsMethodKind; +import com.redhat.qute.commons.jaxrs.JaxRsParamKind; +import com.redhat.qute.commons.jaxrs.RestParam; import com.redhat.qute.ls.api.QuteDataModelProjectProvider; import com.redhat.qute.ls.api.QuteUserTagProvider; @@ -287,6 +291,31 @@ private void createSourceTypes(List cache) { registerForReflectionAnnotation = new RegisterForReflectionAnnotation(); registerForReflectionAnnotation.setMethods(false); itemWithRegisterForReflectionNoMethods.setRegisterForReflectionAnnotation(registerForReflectionAnnotation); + + // Renarde controller + createResolvedJavaTypeInfo( + "javax.ws.rs.core.Response", cache, true); + ResolvedJavaTypeInfo renardeLogin = createResolvedJavaTypeInfo( + "rest.Login", cache, false, "io.quarkiverse.renarde.oidc.ControllerWithUser"); + + JavaMethodInfo loginMethod = registerMethod("login() : io.quarkus.qute.TemplateInstance", renardeLogin); + loginMethod.setJaxRsMethodKind(JaxRsMethodKind.POST); + + JavaMethodInfo manualLoginMethod = registerMethod( + "manualLogin(userName : java.lang.String, password : java.lang.String, webAuthnResponse : io.quarkus.security.webauthn.WebAuthnLoginResponse, ctx : io.vertx.ext.web.RoutingContext) : javax.ws.rs.core.Response", + renardeLogin); + manualLoginMethod.setJaxRsMethodKind(JaxRsMethodKind.POST); + Map restParameters = new HashMap<>(); + restParameters.put("userName", new RestParam("userName", JaxRsParamKind.FORM, false)); + restParameters.put("password", new RestParam("password", JaxRsParamKind.FORM, false)); + manualLoginMethod.setRestParameters(restParameters); + + JavaMethodInfo confirmMethod = registerMethod("confirm(confirmationCode : java.lang.String) : void", + renardeLogin); + restParameters = new HashMap<>(); + restParameters.put("confirmationCode", new RestParam("confirmationCode", JaxRsParamKind.PATH, false)); + confirmMethod.setJaxRsMethodKind(JaxRsMethodKind.GET); + confirmMethod.setRestParameters(restParameters); } @Override @@ -440,6 +469,9 @@ protected List createValueResolvers() { resolvers.add(createValueResolver(null, "GLOBAL", null, "org.acme.Bean", "bean : java.lang.String", ValueResolverKind.TemplateGlobal, true)); + // Renarde controller + resolvers.add(createValueResolver("uri", "Login", null, "rest.Login", "rest.Login", + ValueResolverKind.Renarde, false, false)); return resolvers; } diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionTest.java index 1b9fe9bee..4246ee961 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionTest.java @@ -300,14 +300,15 @@ public void completionInExpressionWithOnlyStartBracket() throws Exception { String template = "{@org.acme.Item item}\r\n" + // "Item: {|"; testCompletionFor(template, // - 6, // + 7, // c("item", "item", r(1, 7, 1, 7)), // c("inject:bean", "inject:bean", r(1, 7, 1, 7)), // c("inject:plexux", "inject:plexux", r(1, 7, 1, 7)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(1, 7, 1, 7)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(1, 7, 1, 7)), // - c("GLOBAL", "GLOBAL", r(1, 7, 1, 7))); + c("GLOBAL", "GLOBAL", r(1, 7, 1, 7)), // + c("uri:Login", "uri:Login", r(1, 7, 1, 7))); } @@ -328,14 +329,15 @@ public void noCompletionInsideSeveralBrackets() throws Exception { // three brackets -> expression template = "{@org.acme.Item item}\r\n" + // "Item: {{{|"; - testCompletionFor(template, 6, // + testCompletionFor(template, 7, // c("item", "item", r(1, 9, 1, 9)), // c("inject:bean", "inject:bean", r(1, 9, 1, 9)), // c("inject:plexux", "inject:plexux", r(1, 9, 1, 9)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(1, 9, 1, 9)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(1, 9, 1, 9)), // - c("GLOBAL", "GLOBAL", r(1, 9, 1, 9))); + c("GLOBAL", "GLOBAL", r(1, 9, 1, 9)), // + c("uri:Login", "uri:Login", r(1, 9, 1, 9))); } @Test @@ -364,13 +366,14 @@ public void completionInsideMethodParameter() throws Exception { public void globalVariablesObjectPart() throws Exception { String template = "{|"; testCompletionFor(template, // - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 1, 0, 1)), // c("inject:plexux", "inject:plexux", r(0, 1, 0, 1)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(0, 1, 0, 1)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(0, 1, 0, 1)), // - c("GLOBAL", "GLOBAL", r(0, 1, 0, 1))); + c("GLOBAL", "GLOBAL", r(0, 1, 0, 1)), // + c("uri:Login", "uri:Login", r(0, 1, 0, 1))); } diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithEachSectionTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithEachSectionTest.java index e2f0f37aa..618517178 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithEachSectionTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithEachSectionTest.java @@ -193,26 +193,28 @@ public void inIterable() throws Exception { String template = "{@java.util.List items}\r\n" + // " \r\n" + // "{#each |}"; - testCompletionFor(template, 6, // + testCompletionFor(template, 7, // c("items", "items", r(2, 7, 2, 7)), // c("inject:bean", "inject:bean", r(2, 7, 2, 7)), // c("inject:plexux", "inject:plexux", r(2, 7, 2, 7)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(2, 7, 2, 7)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(2, 7, 2, 7)), // - c("GLOBAL", "GLOBAL", r(2, 7, 2, 7))); + c("GLOBAL", "GLOBAL", r(2, 7, 2, 7)), // + c("uri:Login", "uri:Login", r(2, 7, 2, 7))); template = "{@java.util.List items}\r\n" + // " \r\n" + // "{#each |"; - testCompletionFor(template, 6, // + testCompletionFor(template, 7, // c("items", "items", r(2, 7, 2, 7)), // c("inject:bean", "inject:bean", r(2, 7, 2, 7)), // c("inject:plexux", "inject:plexux", r(2, 7, 2, 7)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(2, 7, 2, 7)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(2, 7, 2, 7)), // - c("GLOBAL", "GLOBAL", r(2, 7, 2, 7))); + c("GLOBAL", "GLOBAL", r(2, 7, 2, 7)), // + c("uri:Login", "uri:Login", r(2, 7, 2, 7))); } diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithForSectionTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithForSectionTest.java index a6c6dde50..042df5818 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithForSectionTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithForSectionTest.java @@ -216,14 +216,15 @@ public void noCompletionInElseBlock() throws Exception { "{#else}\r\n" + // " {|} \r\n" + // <-- here items is only available because it is on #else block "{/for}"; - testCompletionFor(template, 6, // + testCompletionFor(template, 7, // c("items", "items", r(4, 2, 4, 2)), // c("inject:bean", "inject:bean", r(4, 2, 4, 2)), // c("inject:plexux", "inject:plexux", r(4, 2, 4, 2)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(4, 2, 4, 2)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(4, 2, 4, 2)), // - c("GLOBAL", "GLOBAL", r(4, 2, 4, 2))); + c("GLOBAL", "GLOBAL", r(4, 2, 4, 2)), // + c("uri:Login", "uri:Login", r(4, 2, 4, 2))); } @Test diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithLetSectionTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithLetSectionTest.java index 9d79c9f7b..bddbde3d6 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithLetSectionTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithLetSectionTest.java @@ -159,13 +159,14 @@ public void completionInMethodParameter() throws Exception { " {foo.method(n|}\r\n" + // "{/let}"; testCompletionFor(template, // - 6, // + 7, // c("name", "name", r(1, 13, 1, 14)), // c("inject:bean", "inject:bean", r(1, 13, 1, 14)), // c("inject:plexux", "inject:plexux", r(1, 13, 1, 14)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(1, 13, 1, 14)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(1, 13, 1, 14)), // - c("GLOBAL", "GLOBAL", r(1, 13, 1, 14))); + c("GLOBAL", "GLOBAL", r(1, 13, 1, 14)), // + c("uri:Login", "uri:Login", r(1, 13, 1, 14))); } } \ No newline at end of file diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithNamespaceTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithNamespaceTest.java index 0d032febf..46f873158 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithNamespaceTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInExpressionWithNamespaceTest.java @@ -34,7 +34,7 @@ public void dataNamespace() throws Exception { " {|}\r\n" + // "{/let}"; testCompletionFor(template, // - 7, // name and item + 8, // name and item c("item", "item", r(2, 3, 2, 3)), // c("name", "name", r(2, 3, 2, 3)), // c("inject:bean", "inject:bean", r(2, 3, 2, 3)), // @@ -42,7 +42,8 @@ public void dataNamespace() throws Exception { c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(2, 3, 2, 3)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(2, 3, 2, 3)), // - c("GLOBAL", "GLOBAL", r(2, 3, 2, 3))); + c("GLOBAL", "GLOBAL", r(2, 3, 2, 3)), // + c("uri:Login", "uri:Login", r(2, 3, 2, 3))); template = "{@org.acme.Item item}\r\n" + // "{#let name=123 }\r\n" + // @@ -111,13 +112,14 @@ public void injectNamespace() throws Exception { public void namespaceResolver() throws Exception { String template = "{|}"; testCompletionFor(template, // - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 1, 0, 1)), // c("inject:plexux", "inject:plexux", r(0, 1, 0, 1)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(0, 1, 0, 1)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(0, 1, 0, 1)), // - c("GLOBAL", "GLOBAL", r(0, 1, 0, 1))); + c("GLOBAL", "GLOBAL", r(0, 1, 0, 1)), // + c("uri:Login", "uri:Login", r(0, 1, 0, 1))); } @Test @@ -139,13 +141,14 @@ public void namespaceResolverAfterNamespacePart() throws Exception { public void orpheanColonSpace() throws Exception { String template = " {inject :|}"; testCompletionFor(template, // - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 10, 0, 10)), // c("inject:plexux", "inject:plexux", r(0, 10, 0, 10)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(0, 10, 0, 10)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(0, 10, 0, 10)), // - c("GLOBAL", "GLOBAL", r(0, 10, 0, 10))); + c("GLOBAL", "GLOBAL", r(0, 10, 0, 10)), // + c("uri:Login", "uri:Login", r(0, 10, 0, 10))); } } \ No newline at end of file diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInInfixNotationTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInInfixNotationTest.java index 9fbcbfa54..d36b95778 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInInfixNotationTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInInfixNotationTest.java @@ -84,28 +84,30 @@ public void elvisOperator() throws Exception { // Infix notation : only methods with one parameter of String class testCompletionFor(template, // - 6, // + 7, // c("item", "item", r(1, 14, 1, 14)), // c("inject:bean", "inject:bean", r(1, 14, 1, 14)), // c("inject:plexux", "inject:plexux", r(1, 14, 1, 14)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(1, 14, 1, 14)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(1, 14, 1, 14)), // - c("GLOBAL", "GLOBAL", r(1, 14, 1, 14))); - + c("GLOBAL", "GLOBAL", r(1, 14, 1, 14)), // + c("uri:Login", "uri:Login", r(1, 14, 1, 14))); + template = "{@org.acme.Item item}\r\n" + // "{item.name ?: item.name :|}"; // Infix notation : only methods with one parameter of String class testCompletionFor(template, // - 6, // + 7, // c("item", "item", r(1, 25, 1, 25)), // c("inject:bean", "inject:bean", r(1, 25, 1, 25)), // c("inject:plexux", "inject:plexux", r(1, 25, 1, 25)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(1, 25, 1, 25)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(1, 25, 1, 25)), // - c("GLOBAL", "GLOBAL", r(1, 25, 1, 25))); + c("GLOBAL", "GLOBAL", r(1, 25, 1, 25)), // + c("uri:Login", "uri:Login", r(1, 25, 1, 25))); } - + } \ No newline at end of file diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInSectionTagTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInSectionTagTest.java index 54e1a79b9..50df4cc59 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInSectionTagTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInSectionTagTest.java @@ -33,23 +33,25 @@ public void openBracket() throws Exception { // Without snippet testCompletionFor(template, // false, // no snippet support - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 1, 0, 1)), // c("inject:plexux", "inject:plexux", r(0, 1, 0, 1)), // c("config:*(propertyName : String) : Object", "config:propertyName", r(0, 1, 0, 1)), c("config:property(propertyName : String) : Object", "config:property(propertyName)", r(0, 1, 0, 1)), // - c("GLOBAL", "GLOBAL", r(0, 1, 0, 1))); + c("GLOBAL", "GLOBAL", r(0, 1, 0, 1)), // + c("uri:Login", "uri:Login", r(0, 1, 0, 1))); // With snippet support testCompletionFor(template, // true, // snippet support - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 1, 0, 1)), // c("inject:plexux", "inject:plexux", r(0, 1, 0, 1)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(0, 1, 0, 1)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(0, 1, 0, 1)), // - c("GLOBAL", "GLOBAL", r(0, 1, 0, 1))); + c("GLOBAL", "GLOBAL", r(0, 1, 0, 1)), // + c("uri:Login", "uri:Login", r(0, 1, 0, 1))); } @Test @@ -59,23 +61,25 @@ public void openAndCloseBracket() throws Exception { // Without snippet testCompletionFor(template, // false, // no snippet support - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 1, 0, 1)), // c("inject:plexux", "inject:plexux", r(0, 1, 0, 1)), // c("config:*(propertyName : String) : Object", "config:propertyName", r(0, 1, 0, 1)), c("config:property(propertyName : String) : Object", "config:property(propertyName)", r(0, 1, 0, 1)), // - c("GLOBAL", "GLOBAL", r(0, 1, 0, 1))); + c("GLOBAL", "GLOBAL", r(0, 1, 0, 1)), // + c("uri:Login", "uri:Login", r(0, 1, 0, 1))); // With snippet support testCompletionFor(template, // true, // snippet support - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 1, 0, 1)), // c("inject:plexux", "inject:plexux", r(0, 1, 0, 1)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(0, 1, 0, 1)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(0, 1, 0, 1)), // - c("GLOBAL", "GLOBAL", r(0, 1, 0, 1))); + c("GLOBAL", "GLOBAL", r(0, 1, 0, 1)), // + c("uri:Login", "uri:Login", r(0, 1, 0, 1))); } @Test @@ -85,23 +89,25 @@ public void openAndCloseBracketWithSpaces() throws Exception { // Without snippet testCompletionFor(template, // false, // no snippet support - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 1, 0, 1)), // c("inject:plexux", "inject:plexux", r(0, 1, 0, 1)), // c("config:*(propertyName : String) : Object", "config:propertyName", r(0, 1, 0, 1)), c("config:property(propertyName : String) : Object", "config:property(propertyName)", r(0, 1, 0, 1)), // - c("GLOBAL", "GLOBAL", r(0, 1, 0, 1))); + c("GLOBAL", "GLOBAL", r(0, 1, 0, 1)), // + c("uri:Login", "uri:Login", r(0, 1, 0, 1))); // With snippet support testCompletionFor(template, // true, // snippet support - 5, // + 6, // c("inject:bean", "inject:bean", r(0, 1, 0, 1)), // c("inject:plexux", "inject:plexux", r(0, 1, 0, 1)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(0, 1, 0, 1)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(0, 1, 0, 1)), // - c("GLOBAL", "GLOBAL", r(0, 1, 0, 1))); + c("GLOBAL", "GLOBAL", r(0, 1, 0, 1)), // + c("uri:Login", "uri:Login", r(0, 1, 0, 1))); } @Test diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInUserTagTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInUserTagTest.java index 4e145cf30..5a39ed68b 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInUserTagTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionInUserTagTest.java @@ -89,20 +89,21 @@ public void specialKeys() throws Exception { // In qute template testCompletionFor(template, // - 6, // + 7, // c("item", "item", r(1, 1, 1, 1)), // c("inject:bean", "inject:bean", r(1, 1, 1, 1)), // c("inject:plexux", "inject:plexux", r(1, 1, 1, 1)), // c("config:*(propertyName : String) : Object", "config:${1:propertyName}$0", r(1, 1, 1, 1)), c("config:property(propertyName : String) : Object", "config:property(${1:propertyName})$0", r(1, 1, 1, 1)), // - c("GLOBAL", "GLOBAL", r(1, 1, 1, 1))); + c("GLOBAL", "GLOBAL", r(1, 1, 1, 1)), // + c("uri:Login", "uri:Login", r(1, 1, 1, 1))); // In user tag testCompletionFor(template, // "src/main/resources/templates/tags/form.html", // "tags/form", // - 5 /* item, inject:bean, config:getConfigProperty */ + 2 /* it, nested-content */ + 1 /* + 6 /* item, inject:bean, config:getConfigProperty */ + 2 /* it, nested-content */ + 1 /* * global * variables */, // @@ -113,7 +114,8 @@ public void specialKeys() throws Exception { c("config:property(propertyName : String) : Object", "config:property(propertyName)", r(1, 1, 1, 1)), // c("GLOBAL", "GLOBAL", r(1, 1, 1, 1)), // c("it", "it", r(1, 1, 1, 1)), // - c("nested-content", "nested-content", r(1, 1, 1, 1))); + c("nested-content", "nested-content", r(1, 1, 1, 1)), // + c("uri:Login", "uri:Login", r(1, 1, 1, 1))); } diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionWithCheckedTemplateTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionWithCheckedTemplateTest.java index 5b509bf7f..79ad50356 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionWithCheckedTemplateTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionWithCheckedTemplateTest.java @@ -45,6 +45,6 @@ public void checkedTemplateWithOverlappingTemplateParam() throws Exception { "Item: {|"; testCompletionFor(template, // "src/main/resources/templates/ItemResource/items.qute.html", // - "ItemResource/Items", 6, c("items", "items", r(1, 7, 1, 7))); + "ItemResource/Items", 7, c("items", "items", r(1, 7, 1, 7))); } } \ No newline at end of file diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/RenardeCompletionInExpressionTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/RenardeCompletionInExpressionTest.java new file mode 100644 index 000000000..3ca96a2b5 --- /dev/null +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/RenardeCompletionInExpressionTest.java @@ -0,0 +1,51 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.services.completions; + +import static com.redhat.qute.QuteAssert.c; +import static com.redhat.qute.QuteAssert.r; +import static com.redhat.qute.QuteAssert.testCompletionFor; + +import org.junit.jupiter.api.Test; + +/** + * Tests for Renarde completion in uri / urabs expression. + * + * @author Angelo ZERR + * + */ +public class RenardeCompletionInExpressionTest { + + @Test + public void controllerCompletion() throws Exception { + String template = "{uri:|}"; + testCompletionFor(template, // + c("Login", "Login", r(0, 5, 0, 5))); + } + + @Test + public void methodsOfController() throws Exception { + String template = "{uri:Login.|}"; + testCompletionFor(template, // + 8, // + c("login() : TemplateInstance", "login", r(0, 11, 0, 11)), // + c("manualLogin(userName : String, password : String, webAuthnResponse : WebAuthnLoginResponse, ctx : RoutingContext) : Response", + "manualLogin()$0", r(0, 11, 0, 11)), // + c("confirm(confirmationCode : String) : void", "confirm(${1:confirmationCode})$0", r(0, 11, 0, 11)), // + c("orEmpty(base : T) : List", "orEmpty", r(0, 11, 0, 11)), // + c("safe(base : Object) : RawString", "safe", r(0, 11, 0, 11)), // + c("raw(base : Object) : RawString", "raw", r(0, 11, 0, 11)), // + c("or(base : T, arg : Object) : T", "or(${1:arg})$0", r(0, 11, 0, 11)), // + c("ifTruthy(base : T, arg : Object) : T", "ifTruthy(${1:arg})$0", r(0, 11, 0, 11))); + } + +} \ No newline at end of file diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/diagnostics/RenardeDiagnosticsTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/diagnostics/RenardeDiagnosticsTest.java new file mode 100644 index 000000000..0a2739a74 --- /dev/null +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/diagnostics/RenardeDiagnosticsTest.java @@ -0,0 +1,68 @@ +/******************************************************************************* +* Copyright (c) 2023 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.qute.services.diagnostics; + +import static com.redhat.qute.QuteAssert.d; +import static com.redhat.qute.QuteAssert.testDiagnosticsFor; + +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.junit.jupiter.api.Test; + +/** + * Test with Renarde uri/uriabs. + * + * @author Angelo ZERR + * + */ +public class RenardeDiagnosticsTest { + + @Test + public void badController() throws Exception { + testDiagnosticsFor("{uri:XXXX.confirm()}", // + d(0, 5, 0, 9, QuteErrorCode.UndefinedObject, + "`XXXX` cannot be resolved to an object.", + DiagnosticSeverity.Warning), + d(0, 10, 0, 17, QuteErrorCode.UnknownMethod, + "`confirm` cannot be resolved or is not a method of `null` Java type.", + DiagnosticSeverity.Error)); + + } + + @Test + public void confirm() throws Exception { + testDiagnosticsFor("{uri:Login.confirm('ok')}"); + testDiagnosticsFor("{uri:Login.confirm()}", // + d(0, 11, 0, 18, QuteErrorCode.InvalidMethodParameter, + "The method `confirm(String)` in the type `Login` is not applicable for the arguments `()`.", + DiagnosticSeverity.Error)); + testDiagnosticsFor("{uri:Login.confirm(0)}", // + d(0, 11, 0, 18, QuteErrorCode.InvalidMethodParameter, + "The method `confirm(String)` in the type `Login` is not applicable for the arguments `(Integer)`.", + DiagnosticSeverity.Error)); + testDiagnosticsFor("{uri:Login.confirm('ok',0)}", // + d(0, 11, 0, 18, QuteErrorCode.InvalidMethodParameter, + "The method `confirm(String)` in the type `Login` is not applicable for the arguments `(String, Integer)`.", + DiagnosticSeverity.Error)); + + } + + @Test + public void manualLogin() throws Exception { + testDiagnosticsFor("{uri:Login.manualLogin()}"); + testDiagnosticsFor("{uri:Login.manualLogin('nok')}", // + d(0, 11, 0, 22, QuteErrorCode.InvalidMethodParameter, + "The method `manualLogin(String, String, WebAuthnLoginResponse, RoutingContext)` in the type `Login` is not applicable for the arguments `(String)`.", + DiagnosticSeverity.Error)); + + } + +}