diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..84bcec752 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +dependency-reduced-pom.xml +.settings +.vscode +.factorypath +log.txt +.classpath +.project +unpack +target +.attach_pid* diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..9cc84ea9b Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..b573bb50d --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 000000000..c640d7481 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# spring-boot-feature + +This is a Graal feature than enables Spring applications to be compiled using the Graal +native-image command. Once compiled they will have instant startup! + +This feature supports: +- Graal 19.2.0 +- Spring Boot 2.2.0.M5 +- Spring Framework 5.2.0.RC1 + +To try it out, install Graal 19.2 from: https://github.com/oracle/graal/releases + +Then build the root feature project with: + +`mvn clean package` + +Now go into the samples subfolder. Each folder in there is using a piece of Spring +technology. Within each is a mini project and a `compile.sh` script - the script +will call the native-image command passing the feature on the classpath, the executable +produced in each case should start instantly. + diff --git a/clean.sh b/clean.sh new file mode 100755 index 000000000..dc7acfbd0 --- /dev/null +++ b/clean.sh @@ -0,0 +1,46 @@ +mvn clean + +cd samples/commandlinerunner +mvn clean +rm clr +rm -rf unpack + +cd ../commandlinerunner-maven +mvn clean + +cd ../vanilla-grpc +mvn clean +rm -rf unpack +rm grpc + +cd ../vanilla-jpa +mvn clean +rm -rf unpack +rm jpa + +cd ../vanilla-orm +mvn clean +rm -rf unpack +rm orm + +cd ../vanilla-rabbit +mvn clean +rm -rf unpack +rm rabbit + +cd ../vanilla-thymeleaf +mvn clean +rm -rf unpack +rm thymeleaf + +cd ../vanilla-tx +mvn clean +rm -rf unpack +rm tx + +cd ../webflux-netty +mvn clean +rm -rf unpack +rm webflux-netty + +cd ../.. diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..5bf251c07 --- /dev/null +++ b/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, 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)`" + # TODO classpath? +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 + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +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 + +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/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..019bd74d7 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my 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 + +%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/pom.xml b/pom.xml new file mode 100644 index 000000000..3665e8d5d --- /dev/null +++ b/pom.xml @@ -0,0 +1,190 @@ + + 4.0.0 + + org.springframework + spring-graal-feature + 0.5.0.BUILD-SNAPSHOT + jar + + spring-graal-feature + http://maven.apache.org + + + UTF-8 + 1.8 + 1.8 + + + + + + org.springframework.boot + spring-boot + 2.2.0.M5 + provided + true + + + org.springframework + spring-orm + 5.2.0.RC1 + provided + true + + + org.hibernate + hibernate-core + 5.4.2.Final + provided + true + + + jakarta.validation + jakarta.validation-api + 2.0.1 + provided + true + + + + com.oracle.substratevm + graal-hotspot-library + 19.2.0 + provided + true + + + org.ow2.asm + asm-tree + 7.1 + true + + + junit + junit + 4.12 + test + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-json-shade-source + generate-sources + + add-source + + + + ${basedir}/src/json-shade/java + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + package + + shade + + + + + org.objectweb.asm + sbg.asm + + + + + org.ow2.asm:asm-tree + org.ow2.asm:asm + + + + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + diff --git a/samples/commandlinerunner-maven/README.md b/samples/commandlinerunner-maven/README.md new file mode 100644 index 000000000..b5b8e6ca4 --- /dev/null +++ b/samples/commandlinerunner-maven/README.md @@ -0,0 +1,42 @@ +This is a variant of the commandlinerunner sample that uses maven to drive the native-image construction. + +Ensure you have: +- `mvn install`ed the spring-graal-feature project +- have the graal JDK installed and JAVA_HOME set appropriately (eg. `/Users/aclement/installs/graalvm-ce-19.2.0/Contents/Home`) + +Then you can: + +`mvn clean package` + +This will build the project normally and give you a boot executable jar (as normal) + + +`mvn -Pgraal clean package` + +This will compile the project then drive it through native-image, producing an executable called `clr` in the target folder. + +Notes: +- without the compile script we need to pass the options to the native-image command. This is done via the file (currently) in `src/main/resources/META-INF/native-image/native-image.properties`: +``` +ImageName=clr +Args= -H:+ReportExceptionStackTraces --no-fallback --allow-incomplete-classpath --report-unsupported-elements-at-runtime +``` +- the script also used to pass the initial class to the command. That is now done by setting `` in the properties section of the pom. + +``` +time ./target/clr + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: + +CLR running! + +real 0m0.050s +user 0m0.030s +sys 0m0.015s +``` diff --git a/samples/commandlinerunner-maven/pom.xml b/samples/commandlinerunner-maven/pom.xml new file mode 100644 index 000000000..99d823f93 --- /dev/null +++ b/samples/commandlinerunner-maven/pom.xml @@ -0,0 +1,143 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + com.example + commandlinerunner + 0.0.1-SNAPSHOT + commandlinerunner + Demo project for Spring Boot + + 1.8 + com.example.commandlinerunner.CommandlinerunnerApplication + + + + org.springframework.boot + spring-boot-starter + + + org.springframework + spring-graal-feature + 0.5.0.BUILD-SNAPSHOT + + + org.springframework + spring-context-indexer + + + + + jar + + true + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + graal + + + + com.oracle.substratevm + native-image-maven-plugin + 19.2.0 + + + + native-image + + package + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + diff --git a/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CLR.java b/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CLR.java new file mode 100644 index 000000000..b8c7dcb69 --- /dev/null +++ b/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CLR.java @@ -0,0 +1,15 @@ +package com.example.commandlinerunner; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +//(proxyBeanMethods=false) +public class CLR implements CommandLineRunner { + + @Override + public void run(String... args) throws Exception { + System.out.println("CLR running!"); + } + +} diff --git a/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java b/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java new file mode 100644 index 000000000..70f687edd --- /dev/null +++ b/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java @@ -0,0 +1,35 @@ +package com.example.commandlinerunner; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication(proxyBeanMethods=false) +public class CommandlinerunnerApplication { + +// private static final Logger LOGGER; +// static { +// try { +// LogManager.getLogManager().readConfiguration(CommandlinerunnerApplication.class.getResourceAsStream("logging.properties")); +// } catch (SecurityException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// LOGGER = Logger.getLogger(CommandlinerunnerApplication.class.getName()); +// } + + public static void main(String[] args) { +// LOGGER.fine("foo"); + SpringApplication.run(CommandlinerunnerApplication.class, args); + } + +// @Bean +// public CommandLineRunner clr1() { +// return new CLR(); +// } + +} diff --git a/samples/commandlinerunner-maven/src/main/resources/META-INF/native-image/native-image.properties b/samples/commandlinerunner-maven/src/main/resources/META-INF/native-image/native-image.properties new file mode 100644 index 000000000..a0d786603 --- /dev/null +++ b/samples/commandlinerunner-maven/src/main/resources/META-INF/native-image/native-image.properties @@ -0,0 +1,2 @@ +ImageName=clr +Args= -H:+ReportExceptionStackTraces --no-fallback --allow-incomplete-classpath --report-unsupported-elements-at-runtime diff --git a/samples/commandlinerunner-maven/src/main/resources/application.properties b/samples/commandlinerunner-maven/src/main/resources/application.properties new file mode 100644 index 000000000..f85e7ff2e --- /dev/null +++ b/samples/commandlinerunner-maven/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.datasource.jmx-enabled=false +logging.level.root=INFO + diff --git a/samples/commandlinerunner-maven/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java b/samples/commandlinerunner-maven/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java new file mode 100644 index 000000000..1f31be55e --- /dev/null +++ b/samples/commandlinerunner-maven/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java @@ -0,0 +1,18 @@ +/* +package com.example.commandlinerunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class CommandlinerunnerApplicationTests { + + @Test + public void contextLoads() { + } + +} +*/ diff --git a/samples/commandlinerunner/README.md b/samples/commandlinerunner/README.md new file mode 100644 index 000000000..5489a9858 --- /dev/null +++ b/samples/commandlinerunner/README.md @@ -0,0 +1,23 @@ +Very basic spring boot project. Using a CommandLineRunner bean. + +Run the `./compile.sh` script to run the maven build and run native-image on it. + +Then you can launch the `./clr` executable: + +``` + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: + +Sep 05, 2019 8:53:20 AM org.springframework.boot.StartupInfoLogger logStarting +INFO: Starting CommandlinerunnerApplication on Andys-MacBook-Pro-2018.local with PID 18483 (/Users/aclement/gits3/spring-graal-feature/samples/commandlinerunner/clr started by aclement in /Users/aclement/gits3/spring-graal-feature/samples/commandlinerunner) +Sep 05, 2019 8:53:20 AM org.springframework.boot.SpringApplication logStartupProfileInfo +INFO: No active profile set, falling back to default profiles: default +Sep 05, 2019 8:53:20 AM org.springframework.boot.StartupInfoLogger logStarted +INFO: Started CommandlinerunnerApplication in 0.045 seconds (JVM running for 0.048) +CLR running! +``` diff --git a/samples/commandlinerunner/compile.sh b/samples/commandlinerunner/compile.sh new file mode 100755 index 000000000..210d1260d --- /dev/null +++ b/samples/commandlinerunner/compile.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +../../mvnw clean install + +export JAR="commandlinerunner-0.0.1-SNAPSHOT.jar" +rm clr +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=clr \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP com.example.commandlinerunner.CommandlinerunnerApplication + +mv clr ../../.. + +printf "\n\nJava exploded jar\n" +time java -classpath $CP com.example.commandlinerunner.CommandlinerunnerApplication + +printf "\n\nCompiled app (clr)\n" +cd ../../.. +time ./clr + diff --git a/samples/commandlinerunner/pom.xml b/samples/commandlinerunner/pom.xml new file mode 100644 index 000000000..f8b262024 --- /dev/null +++ b/samples/commandlinerunner/pom.xml @@ -0,0 +1,155 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + com.example + commandlinerunner + 0.0.1-SNAPSHOT + commandlinerunner + Demo project for Spring Boot + + 1.8 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework + spring-context-indexer + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + maven-dependency-plugin + + + generate-sources + + build-classpath + + + maven.compile.classpath + : + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.apache.maven.plugins + + + maven-dependency-plugin + + + [3.1.1,) + + + build-classpath + + + + + + + + + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + diff --git a/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CLR.java b/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CLR.java new file mode 100644 index 000000000..b8c7dcb69 --- /dev/null +++ b/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CLR.java @@ -0,0 +1,15 @@ +package com.example.commandlinerunner; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +//(proxyBeanMethods=false) +public class CLR implements CommandLineRunner { + + @Override + public void run(String... args) throws Exception { + System.out.println("CLR running!"); + } + +} diff --git a/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java b/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java new file mode 100644 index 000000000..70f687edd --- /dev/null +++ b/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java @@ -0,0 +1,35 @@ +package com.example.commandlinerunner; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication(proxyBeanMethods=false) +public class CommandlinerunnerApplication { + +// private static final Logger LOGGER; +// static { +// try { +// LogManager.getLogManager().readConfiguration(CommandlinerunnerApplication.class.getResourceAsStream("logging.properties")); +// } catch (SecurityException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// LOGGER = Logger.getLogger(CommandlinerunnerApplication.class.getName()); +// } + + public static void main(String[] args) { +// LOGGER.fine("foo"); + SpringApplication.run(CommandlinerunnerApplication.class, args); + } + +// @Bean +// public CommandLineRunner clr1() { +// return new CLR(); +// } + +} diff --git a/samples/commandlinerunner/src/main/resources/application.yml b/samples/commandlinerunner/src/main/resources/application.yml new file mode 100644 index 000000000..c45f53038 --- /dev/null +++ b/samples/commandlinerunner/src/main/resources/application.yml @@ -0,0 +1,4 @@ +logging: + level: + root: INFO + diff --git a/samples/commandlinerunner/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java b/samples/commandlinerunner/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java new file mode 100644 index 000000000..1f31be55e --- /dev/null +++ b/samples/commandlinerunner/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java @@ -0,0 +1,18 @@ +/* +package com.example.commandlinerunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class CommandlinerunnerApplicationTests { + + @Test + public void contextLoads() { + } + +} +*/ diff --git a/samples/vanilla-grpc/README.adoc b/samples/vanilla-grpc/README.adoc new file mode 100644 index 000000000..5ede70099 --- /dev/null +++ b/samples/vanilla-grpc/README.adoc @@ -0,0 +1,32 @@ +A GRPC version of Josh's https://spring.io/blog/2015/03/22/using-google-protocol-buffers-with-spring-mvc-based-rest-services[2015 Blog] on Protobuf support in Spring. + +Service reflection: + +``` +$ grpcurl -plaintext localhost:50051 describe demo.Greeter +demo.Greeter is a service: +service Greeter { + rpc Hello ( .demo.CustomerRequest ) returns ( .demo.Customer ); +} +``` + +Hello world: + +``` +$ grpcurl -plaintext -d '{}' localhost:50051 demo.Greeter/Hello +{ + "id": 1, + "firstName": "Josh", + "lastName": "Long" +} +``` + +Native image: + +``` +$ CP=`java -jar $HOME/.m2/repository/org/springframework/boot/experimental/spring-boot-thin-launcher/1.0.22.RELEASE/spring-boot-thin-launcher-1.0.22.RELEASE-exec.jar --thin.archive=target/vanilla-proto-0.0.1-SNAPSHOT.jar --thin.classpath` +$ native-image -Dio.netty.noUnsafe=true --no-server -H:Name=target/demo \ +-H:+ReportExceptionStackTraces --no-fallback --allow-incomplete-classpath --report-unsupported-elements-at-runtime \ +-DremoveUnusedAutoconfig=true -cp $CP \ +--initialize-at-build-time='com.google.protobuf.ExtensionRegistry,com.google.protobuf.ExtensionLite,com.google.protobuf.Extension,io.netty.handler.codec.http2.ReadOnlyHttp2Headers,io.netty.handler.codec.http2.CharSequenceMap,io.netty.handler.codec.http2.Http2Headers$PseudoHeaderName' \ +com.example.ProtoApplication` \ No newline at end of file diff --git a/samples/vanilla-grpc/compile.sh b/samples/vanilla-grpc/compile.sh new file mode 100755 index 000000000..116f2cd8a --- /dev/null +++ b/samples/vanilla-grpc/compile.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +export EXECUTABLE_NAME=grpc + +../../mvnw -DskipTests clean package + +export JAR=`ls -1 target/*.jar` + +rm $EXECUTABLE_NAME +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=$EXECUTABLE_NAME \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP com.example.ProtoApplication + #--debug-attach \ + +mv $EXECUTABLE_NAME ../../.. + +printf "\n\nCompiled app...\n" +cd ../../.. +time ./$EXECUTABLE_NAME + diff --git a/samples/vanilla-grpc/pom.xml b/samples/vanilla-grpc/pom.xml new file mode 100644 index 000000000..6aadf57d3 --- /dev/null +++ b/samples/vanilla-grpc/pom.xml @@ -0,0 +1,132 @@ + + + 4.0.0 + + com.example + vanilla-grpc + 0.0.1-SNAPSHOT + jar + + vanilla-grpc + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + UTF-8 + UTF-8 + 1.8 + 1.5.0.Final + 0.6.1 + 3.7.1 + 1.22.1 + + + + + org.springframework.boot + spring-boot-starter + + + io.grpc + grpc-netty + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + io.grpc + grpc-services + ${grpc.version} + + + + + + + kr.motd.maven + os-maven-plugin + ${os.plugin.version} + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf.plugin.version} + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/samples/vanilla-grpc/src/main/java/com/example/ProtoApplication.java b/samples/vanilla-grpc/src/main/java/com/example/ProtoApplication.java new file mode 100644 index 000000000..a3ca957b8 --- /dev/null +++ b/samples/vanilla-grpc/src/main/java/com/example/ProtoApplication.java @@ -0,0 +1,72 @@ +package com.example; + +import java.io.IOException; +import java.util.logging.Logger; + +import demo.CustomerProtos.Customer; +import demo.CustomerProtos.CustomerRequest; +import demo.GreeterGrpc; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.stub.StreamObserver; + +public class ProtoApplication { + + private static final Logger logger = Logger + .getLogger(ProtoApplication.class.getName()); + + private Server server; + + private void start() throws IOException { + /* The port on which the server should run */ + int port = 50051; + server = ServerBuilder.forPort(port) + .addService(ProtoReflectionService.newInstance()) + .addService(new GreeterImpl()).build().start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM + // shutdown hook. + System.err.println( + "*** shutting down gRPC server since JVM is shutting down"); + ProtoApplication.this.stop(); + System.err.println("*** server shut down"); + } + }); + } + + private void stop() { + if (server != null) { + server.shutdown(); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + public static void main(String[] args) throws Exception { + final ProtoApplication server = new ProtoApplication(); + server.start(); + server.blockUntilShutdown(); + } +} + +class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void hello(CustomerRequest req, StreamObserver responseObserver) { + Customer reply = Customer.newBuilder().setId(1).setFirstName("Josh").setLastName("Long") + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } +} \ No newline at end of file diff --git a/samples/vanilla-grpc/src/main/proto/customer.proto b/samples/vanilla-grpc/src/main/proto/customer.proto new file mode 100644 index 000000000..cea761ebe --- /dev/null +++ b/samples/vanilla-grpc/src/main/proto/customer.proto @@ -0,0 +1,33 @@ +package demo; + +option java_package = "demo"; +option java_outer_classname = "CustomerProtos"; + +service Greeter { + rpc Hello(CustomerRequest) returns (Customer) {} +} + +message CustomerRequest {} + +message Customer { + required int32 id = 1; + required string firstName = 2; + required string lastName = 3; + + enum EmailType { + PRIVATE = 1; + PROFESSIONAL = 2; + } + + message EmailAddress { + required string email = 1; + optional EmailType type = 2 [default = PROFESSIONAL]; + } + + repeated EmailAddress email = 5; +} + +message Organization { + required string name = 1; + repeated Customer customer = 2; +} \ No newline at end of file diff --git a/samples/vanilla-grpc/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-grpc/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..2e6a4a0a2 --- /dev/null +++ b/samples/vanilla-grpc/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,12 @@ +[ + { + "name": "demo.CustomerProtos$Customer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "demo.GreeterGrpc", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] diff --git a/samples/vanilla-grpc/src/main/resources/META-INF/native-image/resource-config.json b/samples/vanilla-grpc/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 000000000..c2b59f99b --- /dev/null +++ b/samples/vanilla-grpc/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,5 @@ +{ + "resources": [ + {"pattern": ".*.proto"} + ] +} \ No newline at end of file diff --git a/samples/vanilla-grpc/src/test/resources/customer.data b/samples/vanilla-grpc/src/test/resources/customer.data new file mode 100644 index 000000000..8cb1bceb9 Binary files /dev/null and b/samples/vanilla-grpc/src/test/resources/customer.data differ diff --git a/samples/vanilla-jpa/compile.sh b/samples/vanilla-jpa/compile.sh new file mode 100755 index 000000000..245709827 --- /dev/null +++ b/samples/vanilla-jpa/compile.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +export EXECUTABLE_NAME=jpa +export JAR="vanilla-jpa-0.0.1.BUILD-SNAPSHOT.jar" + +../../mvnw -DskipTests clean package + +rm $EXECUTABLE_NAME +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=$EXECUTABLE_NAME \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP app.main.SampleApplication + #--debug-attach \ + +mv $EXECUTABLE_NAME ../../.. + +printf "\n\nCompiled app...\n" +cd ../../.. +#time ./orm -Dhibernate.dialect=org.hibernate.dialect.H2Dialect +# Do we need the -D on this one? +time ./$EXECUTABLE_NAME +# -Dhibernate.dialect=org.hibernate.dialect.H2Dialect + diff --git a/samples/vanilla-jpa/pom.xml b/samples/vanilla-jpa/pom.xml new file mode 100644 index 000000000..ab83e62c7 --- /dev/null +++ b/samples/vanilla-jpa/pom.xml @@ -0,0 +1,170 @@ + + + 4.0.0 + + org.springframework.experimental + vanilla-jpa + 0.0.1.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + +org.springframework +spring-context-indexer + + + org.springframework.boot + spring-boot-starter-data-jpa + + + net.bytebuddy + byte-buddy + + + org.javassist + javassist + + + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-webflux + + + io.netty + netty-transport-native-epoll + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + org.hibernate.validator + hibernate-validator + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + 1.8 + app.main.SampleApplication + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.hibernate.orm.tooling + hibernate-enhance-maven-plugin + ${hibernate.version} + + + + true + true + true + true + false + + + enhance + + + + + + + + + + jitpack.io + https://jitpack.io + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + false + + + true + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + true + + + true + + + + + diff --git a/samples/vanilla-jpa/src/main/java/app/main/SampleApplication.java b/samples/vanilla-jpa/src/main/java/app/main/SampleApplication.java new file mode 100644 index 000000000..254f594ff --- /dev/null +++ b/samples/vanilla-jpa/src/main/java/app/main/SampleApplication.java @@ -0,0 +1,68 @@ +package app.main; + +import java.util.Optional; + +import app.main.model.Foo; +import app.main.model.FooRepository; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.server.RouterFunction; + +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@SpringBootApplication(proxyBeanMethods = false) +public class SampleApplication { + + private FooRepository entities; + + public SampleApplication(FooRepository entities) { + this.entities = entities; + } + + @Bean + public CommandLineRunner runner() { + System.err.println("+++++++++++"); + return args -> { + try { + System.err.println("****"); + Optional foo = entities.findById(1L); + System.err.println("****: " + foo); + if (!foo.isPresent()) { + entities.save(new Foo("Hello")); + } + } + catch (Exception e) { + e.printStackTrace(); + } + }; + } + + @Bean + public RouterFunction userEndpoints() { + return route(GET("/"), request -> ok().body( + Mono.fromCallable(this::findOne).log().subscribeOn(Schedulers.elastic()), + Foo.class)); + } + + private Foo findOne() { + try { + return entities.findById(1L).get(); + } + catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} diff --git a/samples/vanilla-jpa/src/main/java/app/main/model/Foo.java b/samples/vanilla-jpa/src/main/java/app/main/model/Foo.java new file mode 100644 index 000000000..27d07f437 --- /dev/null +++ b/samples/vanilla-jpa/src/main/java/app/main/model/Foo.java @@ -0,0 +1,30 @@ +package app.main.model; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Foo { + @Id + @GeneratedValue(strategy=GenerationType.AUTO) + private long id; + private String value; + public Foo() { + } + public Foo(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return String.format( + "Foo[id=%d, value='%s']", + id, value); + } +} diff --git a/samples/vanilla-jpa/src/main/java/app/main/model/FooRepository.java b/samples/vanilla-jpa/src/main/java/app/main/model/FooRepository.java new file mode 100644 index 000000000..d7a079f0d --- /dev/null +++ b/samples/vanilla-jpa/src/main/java/app/main/model/FooRepository.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main.model; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @author Dave Syer + * + */ +public interface FooRepository extends JpaRepository { +} diff --git a/samples/vanilla-jpa/src/main/resources/META-INF/native-image/proxy-config.json b/samples/vanilla-jpa/src/main/resources/META-INF/native-image/proxy-config.json new file mode 100644 index 000000000..ad6e882c1 --- /dev/null +++ b/samples/vanilla-jpa/src/main/resources/META-INF/native-image/proxy-config.json @@ -0,0 +1,3 @@ +[ + ["app.main.model.FooRepository", "org.springframework.data.repository.Repository", "org.springframework.transaction.interceptor.TransactionalProxy", "org.springframework.aop.framework.Advised", "org.springframework.core.DecoratingProxy"] +] \ No newline at end of file diff --git a/samples/vanilla-jpa/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-jpa/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..5d09cac6e --- /dev/null +++ b/samples/vanilla-jpa/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,7 @@ +[ + { + "name": "app.main.model.Foo", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] \ No newline at end of file diff --git a/samples/vanilla-jpa/src/main/resources/application.properties b/samples/vanilla-jpa/src/main/resources/application.properties new file mode 100644 index 000000000..7284df336 --- /dev/null +++ b/samples/vanilla-jpa/src/main/resources/application.properties @@ -0,0 +1,3 @@ +logging.level.root=debug +#spring.jpa.show-sql=true +#spring.data.jpa.repositories.bootstrap-mode=lazy diff --git a/samples/vanilla-jpa/src/main/resources/hibernate.properties b/samples/vanilla-jpa/src/main/resources/hibernate.properties new file mode 100644 index 000000000..35396da22 --- /dev/null +++ b/samples/vanilla-jpa/src/main/resources/hibernate.properties @@ -0,0 +1 @@ +hibernate.bytecode.provider=none \ No newline at end of file diff --git a/samples/vanilla-jpa/src/test/java/app/main/SampleApplicationTests.java b/samples/vanilla-jpa/src/test/java/app/main/SampleApplicationTests.java new file mode 100644 index 000000000..f48c83bed --- /dev/null +++ b/samples/vanilla-jpa/src/test/java/app/main/SampleApplicationTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.main; + +import org.junit.Before; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.server.WebHandler; + +/** + * @author Dave Syer + * + */ +@SpringBootTest +@AutoConfigureWebTestClient +public class SampleApplicationTests { + + @Autowired + private WebHandler webHandler; + + @Autowired + private WebTestClient client; + + @Before + public void init() { + client = WebTestClient.bindToWebHandler(webHandler).build(); + } + + @Test + public void test() { + client.get().uri("/").exchange().expectBody(String.class).isEqualTo("{\"value\":\"Hello\"}"); + } + +} diff --git a/samples/vanilla-orm/.gitignore b/samples/vanilla-orm/.gitignore new file mode 100644 index 000000000..82eca336e --- /dev/null +++ b/samples/vanilla-orm/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.jar b/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..9cc84ea9b Binary files /dev/null and b/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.properties b/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..9dda3b659 --- /dev/null +++ b/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip diff --git a/samples/vanilla-orm/README.md b/samples/vanilla-orm/README.md new file mode 100644 index 000000000..010c35cb4 --- /dev/null +++ b/samples/vanilla-orm/README.md @@ -0,0 +1,2 @@ + +Copy of Daves app from git@github.com:dsyer/vanilla-orm.git diff --git a/samples/vanilla-orm/compile.sh b/samples/vanilla-orm/compile.sh new file mode 100755 index 000000000..cd387ba37 --- /dev/null +++ b/samples/vanilla-orm/compile.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="orm-0.0.1.BUILD-SNAPSHOT.jar" +rm orm +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP com.example.demo.DemoApplication + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=orm \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP app.main.SampleApplication + + #--debug-attach \ +mv orm ../../.. + +printf "\n\nCompiled app (demo)\n" +cd ../../.. +time ./orm -Dhibernate.dialect=org.hibernate.dialect.H2Dialect + diff --git a/samples/vanilla-orm/entity.sh b/samples/vanilla-orm/entity.sh new file mode 100755 index 000000000..84c548cfc --- /dev/null +++ b/samples/vanilla-orm/entity.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +if ! [ "$1" == "" ]; then + cd $1; shift +fi + +n=100 +if ! [ "$1" == "" ]; then + n=$1; shift +fi + +base=src/main/java +pkg=app/main/model +if ! [ "$1" == "" ]; then + pkg=$1; shift +fi + +mkdir -p $base/$pkg + +foo=$base/$pkg/Foo.java +imprt=`echo $pkg | sed -e s,/,.,g` +if ! [ -e $foo ]; then + cat < $foo +package ${imprt}; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Foo { + @Id + @GeneratedValue(strategy=GenerationType.AUTO) + private long id; + private String value; + public Foo() { + } + public Foo(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return String.format( + "Foo[id=%d, value='%s']", + id, value); + } +} +EOF +fi + +for f in $(seq 1 $n); do + target=${foo/Foo/Foo$f} + sed -e "s/Foo/Foo$f/g" $foo > $target +done diff --git a/samples/vanilla-orm/mvnw b/samples/vanilla-orm/mvnw new file mode 100755 index 000000000..5bf251c07 --- /dev/null +++ b/samples/vanilla-orm/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, 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)`" + # TODO classpath? +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 + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +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 + +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/samples/vanilla-orm/mvnw.cmd b/samples/vanilla-orm/mvnw.cmd new file mode 100755 index 000000000..5bf251c07 --- /dev/null +++ b/samples/vanilla-orm/mvnw.cmd @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, 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)`" + # TODO classpath? +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 + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +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 + +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/samples/vanilla-orm/pom.xml b/samples/vanilla-orm/pom.xml new file mode 100644 index 000000000..85c3d8b30 --- /dev/null +++ b/samples/vanilla-orm/pom.xml @@ -0,0 +1,216 @@ + + + 4.0.0 + + org.springframework.experimental + orm + 0.0.1.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework + spring-orm + + + org.hibernate + hibernate-entitymanager + + + net.bytebuddy + byte-buddy + + + org.javassist + javassist + + + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-webflux + + + io.netty + netty-transport-native-epoll + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + org.hibernate.validator + hibernate-validator + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + com.github.mp911de.microbenchmark-runner + microbenchmark-runner-junit5 + 0.1.0.RELEASE + test + + + org.springframework.boot.experimental + spring-boot-thin-launcher + ${thin.version} + test + + + + + 1.8 + app.main.SampleApplication + 1.21 + 1.0.22.RELEASE + + + + + tools.jar + + [1.8,1.9) + + + + com.sun + tools + ${java.version} + system + ${java.home}/../lib/tools.jar + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.hibernate.orm.tooling + hibernate-enhance-maven-plugin + ${hibernate.version} + + + + true + true + true + true + false + + + enhance + + + + + + + + + + jitpack.io + https://jitpack.io + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + false + + + true + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + true + + + true + + + + + diff --git a/samples/vanilla-orm/src/main/java/app/main/BeanCountingApplicationListener.java b/samples/vanilla-orm/src/main/java/app/main/BeanCountingApplicationListener.java new file mode 100644 index 000000000..57bcb6620 --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/BeanCountingApplicationListener.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping; + +public class BeanCountingApplicationListener + implements ApplicationListener, ApplicationContextAware { + + private static Log logger = LogFactory.getLog(BeanCountingApplicationListener.class); + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + if (!event.getApplicationContext().equals(this.context)) { + return; + } + ConfigurableApplicationContext context = event.getApplicationContext(); + log(context); + } + + public void log(ConfigurableApplicationContext context) { + int count = 0; + String id = context.getId(); + List names = new ArrayList<>(); + if (context.getEnvironment().getProperty("initialize", Boolean.class, false)) { + try { + context.getBean(AbstractHandlerMethodMapping.class); + logger.info("Instantiated bean"); + } + catch (BeansException e) { + } + } + while (context != null) { + count += context.getBeanDefinitionCount(); + names.addAll(Arrays.asList(context.getBeanDefinitionNames())); + context = (ConfigurableApplicationContext) context.getParent(); + } + logger.info("Bean count: " + id + "=" + count); + logger.debug("Bean names: " + id + "=" + names); + try { + logger.info("Class count: " + id + "=" + ManagementFactory + .getClassLoadingMXBean().getTotalLoadedClassCount()); + } + catch (Exception e) { + } + } + +} diff --git a/samples/vanilla-orm/src/main/java/app/main/SampleApplication.java b/samples/vanilla-orm/src/main/java/app/main/SampleApplication.java new file mode 100644 index 000000000..4bcfd4ff1 --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/SampleApplication.java @@ -0,0 +1,57 @@ +package app.main; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; + +import app.main.model.Foo; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.server.RouterFunction; + +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@SpringBootApplication(proxyBeanMethods = false) +public class SampleApplication { + + private EntityManagerFactory entities; + + public SampleApplication(EntityManagerFactory entities) { + this.entities = entities; + } + + @Bean + public CommandLineRunner runner() { + return args -> { + EntityManager manager = entities.createEntityManager(); + EntityTransaction transaction = manager.getTransaction(); + transaction.begin(); + Foo foo = manager.find(Foo.class, 1L); + if (foo == null) { + manager.persist(new Foo("Hello")); + } + transaction.commit(); + }; + } + + @Bean + public RouterFunction userEndpoints() { + return route(GET("/"), + request -> ok().body(Mono + .fromCallable( + () -> entities.createEntityManager().find(Foo.class, 1L)) + .subscribeOn(Schedulers.elastic()), Foo.class)); + } + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} diff --git a/samples/vanilla-orm/src/main/java/app/main/ShutdownApplicationListener.java b/samples/vanilla-orm/src/main/java/app/main/ShutdownApplicationListener.java new file mode 100644 index 000000000..afca68768 --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/ShutdownApplicationListener.java @@ -0,0 +1,101 @@ +/* + * Copyright 2019-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.Order; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +@Order(Ordered.HIGHEST_PRECEDENCE) +public class ShutdownApplicationListener + implements ApplicationListener, DisposableBean, + ApplicationContextAware { + + private static final String SHUTDOWN_LISTENER = "SHUTDOWN_LISTENER"; + public static final String MARKER = "Benchmark app stopped"; + + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + if (!event.getApplicationContext().equals(this.context)) { + return; + } + if (isSpringBootApplication(sources(event))) { + ((DefaultListableBeanFactory) event.getApplicationContext().getBeanFactory()) + .registerDisposableBean(SHUTDOWN_LISTENER, this); + } + } + + @Override + public void destroy() throws Exception { + try { + System.out.println(MARKER); + } + catch (Exception e) { + } + } + + private boolean isSpringBootApplication(Set> sources) { + for (Class source : sources) { + if (AnnotatedElementUtils.hasAnnotation(source, + SpringBootConfiguration.class)) { + return true; + } + } + return false; + } + + private Set> sources(ApplicationReadyEvent event) { + Method method = ReflectionUtils.findMethod(SpringApplication.class, + "getAllSources"); + if (method == null) { + method = ReflectionUtils.findMethod(SpringApplication.class, "getSources"); + } + ReflectionUtils.makeAccessible(method); + @SuppressWarnings("unchecked") + Set objects = (Set) ReflectionUtils.invokeMethod(method, + event.getSpringApplication()); + Set> result = new LinkedHashSet<>(); + for (Object object : objects) { + if (object instanceof String) { + object = ClassUtils.resolveClassName((String) object, null); + } + result.add((Class) object); + } + return result; + } +} \ No newline at end of file diff --git a/samples/vanilla-orm/src/main/java/app/main/StartupApplicationListener.java b/samples/vanilla-orm/src/main/java/app/main/StartupApplicationListener.java new file mode 100644 index 000000000..1748659dc --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/StartupApplicationListener.java @@ -0,0 +1,92 @@ +/* + * Copyright 2019-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +public class StartupApplicationListener + implements ApplicationListener, ApplicationContextAware { + + public static final String MARKER = "Benchmark app started"; + private static Log logger = LogFactory.getLog(StartupApplicationListener.class); + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + if (!event.getApplicationContext().equals(this.context)) { + return; + } + if (isSpringBootApplication(sources(event))) { + try { + logger.info(MARKER); + } + catch (Exception e) { + } + } + } + + private boolean isSpringBootApplication(Set> sources) { + for (Class source : sources) { + if (AnnotatedElementUtils.hasAnnotation(source, + SpringBootConfiguration.class)) { + return true; + } + } + return false; + } + + private Set> sources(ApplicationReadyEvent event) { + Method method = ReflectionUtils.findMethod(SpringApplication.class, + "getAllSources"); + if (method == null) { + method = ReflectionUtils.findMethod(SpringApplication.class, "getSources"); + } + ReflectionUtils.makeAccessible(method); + @SuppressWarnings("unchecked") + Set objects = (Set) ReflectionUtils.invokeMethod(method, + event.getSpringApplication()); + Set> result = new LinkedHashSet<>(); + for (Object object : objects) { + if (object instanceof String) { + object = ClassUtils.resolveClassName((String) object, null); + } + result.add((Class) object); + } + return result; + } + +} \ No newline at end of file diff --git a/samples/vanilla-orm/src/main/java/app/main/model/Foo.java b/samples/vanilla-orm/src/main/java/app/main/model/Foo.java new file mode 100644 index 000000000..27d07f437 --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/model/Foo.java @@ -0,0 +1,30 @@ +package app.main.model; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Foo { + @Id + @GeneratedValue(strategy=GenerationType.AUTO) + private long id; + private String value; + public Foo() { + } + public Foo(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return String.format( + "Foo[id=%d, value='%s']", + id, value); + } +} diff --git a/samples/vanilla-orm/src/main/resources/META-INF/spring.factories b/samples/vanilla-orm/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..13e8da539 --- /dev/null +++ b/samples/vanilla-orm/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.context.ApplicationListener=\ +app.main.BeanCountingApplicationListener,\ +app.main.StartupApplicationListener,\ +app.main.ShutdownApplicationListener diff --git a/samples/vanilla-orm/src/main/resources/application.properties b/samples/vanilla-orm/src/main/resources/application.properties new file mode 100644 index 000000000..7813d9f0a --- /dev/null +++ b/samples/vanilla-orm/src/main/resources/application.properties @@ -0,0 +1,3 @@ +logging.level.slim=debug +#spring.jpa.show-sql=true +spring.data.jpa.repositories.bootstrap-mode=lazy diff --git a/samples/vanilla-orm/src/main/resources/hibernate.properties b/samples/vanilla-orm/src/main/resources/hibernate.properties new file mode 100644 index 000000000..35396da22 --- /dev/null +++ b/samples/vanilla-orm/src/main/resources/hibernate.properties @@ -0,0 +1 @@ +hibernate.bytecode.provider=none \ No newline at end of file diff --git a/samples/vanilla-orm/src/test/java/app/main/CaptureSystemOutput.java b/samples/vanilla-orm/src/test/java/app/main/CaptureSystemOutput.java new file mode 100644 index 000000000..4c7f3440d --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/CaptureSystemOutput.java @@ -0,0 +1,253 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.main; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import org.hamcrest.Matcher; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.support.ReflectionSupport; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; + +/** + * {@code @CaptureSystemOutput} is a JUnit JUpiter extension for capturing output to + * {@code System.out} and {@code System.err} with expectations supported via Hamcrest + * matchers. + * + *

Example Usage

+ * + *
+ * {@literal @}Test
+ * {@literal @}CaptureSystemOutput
+ * void systemOut(OutputCapture outputCapture) {
+ *     outputCapture.expect(containsString("System.out!"));
+ *
+ *     System.out.println("Printed to System.out!");
+ * }
+ * 
+ * {@literal @}Test
+ * {@literal @}CaptureSystemOutput
+ * void systemErr(OutputCapture outputCapture) {
+ *     outputCapture.expect(containsString("System.err!"));
+ *
+ *     System.err.println("Printed to System.err!");
+ * }
+ * 
+ * + *

+ * Based on code from Spring Boot's OutputCapture + * rule for JUnit 4 by Phillip Webb and Andy Wilkinson. + * + * @author Sam Brannen + * @author Phillip Webb + * @author Andy Wilkinson + */ +@Target({ TYPE, METHOD }) +@Retention(RUNTIME) +@ExtendWith(CaptureSystemOutput.Extension.class) +public @interface CaptureSystemOutput { + + class Extension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + getOutputCapture(context).captureOutput(); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + OutputCapture outputCapture = getOutputCapture(context); + try { + if (!outputCapture.matchers.isEmpty()) { + String output = outputCapture.toString(); + assertThat(output, allOf(outputCapture.matchers)); + } + } + finally { + outputCapture.releaseOutput(); + } + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + boolean isTestMethodLevel = extensionContext.getTestMethod().isPresent(); + boolean isOutputCapture = parameterContext.getParameter() + .getType() == OutputCapture.class; + return isTestMethodLevel && isOutputCapture; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + return getOutputCapture(extensionContext); + } + + private OutputCapture getOutputCapture(ExtensionContext context) { + return getOrComputeIfAbsent(getStore(context), OutputCapture.class); + } + + private V getOrComputeIfAbsent(Store store, Class type) { + return store.getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); + } + + private Store getStore(ExtensionContext context) { + return context.getStore( + Namespace.create(getClass(), context.getRequiredTestMethod())); + } + + } + + /** + * {@code OutputCapture} captures output to {@code System.out} and {@code System.err}. + * + *

+ * To obtain an instance of {@code OutputCapture}, declare a parameter of type + * {@code OutputCapture} in a JUnit Jupiter {@code @Test}, {@code @BeforeEach}, or + * {@code @AfterEach} method. + * + *

+ * {@linkplain #expect Expectations} are supported via Hamcrest matchers. + * + *

+ * To obtain all output to {@code System.out} and {@code System.err}, simply invoke + * {@link #toString()}. + * + * @author Phillip Webb + * @author Andy Wilkinson + * @author Sam Brannen + */ + static class OutputCapture { + + private final List> matchers = new ArrayList<>(); + + private CaptureOutputStream captureOut; + + private CaptureOutputStream captureErr; + + private ByteArrayOutputStream copy; + + void captureOutput() { + this.copy = new ByteArrayOutputStream(); + this.captureOut = new CaptureOutputStream(System.out, this.copy); + this.captureErr = new CaptureOutputStream(System.err, this.copy); + System.setOut(new PrintStream(this.captureOut)); + System.setErr(new PrintStream(this.captureErr)); + } + + void releaseOutput() { + System.setOut(this.captureOut.getOriginal()); + System.setErr(this.captureErr.getOriginal()); + this.copy = null; + } + + private void flush() { + try { + this.captureOut.flush(); + this.captureErr.flush(); + } + catch (IOException ex) { + // ignore + } + } + + /** + * Verify that the captured output is matched by the supplied {@code matcher}. + * + *

+ * Verification is performed after the test method has executed. + * + * @param matcher the matcher + */ + public void expect(Matcher matcher) { + this.matchers.add(matcher); + } + + /** + * Return all captured output to {@code System.out} and {@code System.err} as a + * single string. + */ + @Override + public String toString() { + flush(); + return this.copy.toString(); + } + + private static class CaptureOutputStream extends OutputStream { + + private final PrintStream original; + + private final OutputStream copy; + + CaptureOutputStream(PrintStream original, OutputStream copy) { + this.original = original; + this.copy = copy; + } + + PrintStream getOriginal() { + return this.original; + } + + @Override + public void write(int b) throws IOException { + this.copy.write(b); + this.original.write(b); + this.original.flush(); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + this.copy.write(b, off, len); + this.original.write(b, off, len); + } + + @Override + public void flush() throws IOException { + this.copy.flush(); + this.original.flush(); + } + + } + + } + +} \ No newline at end of file diff --git a/samples/vanilla-orm/src/test/java/app/main/CsvResultsWriterFactory.java b/samples/vanilla-orm/src/test/java/app/main/CsvResultsWriterFactory.java new file mode 100644 index 000000000..0fcd4626e --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/CsvResultsWriterFactory.java @@ -0,0 +1,181 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import jmh.mbr.core.ResultsWriter; +import jmh.mbr.core.ResultsWriterFactory; +import org.openjdk.jmh.results.Result; +import org.openjdk.jmh.results.RunResult; +import org.openjdk.jmh.runner.format.OutputFormat; +import org.openjdk.jmh.util.FileUtils; +import org.openjdk.jmh.util.ScoreFormatter; +import org.openjdk.jmh.util.Statistics; + +/** + * @author Dave Syer + * + */ +public class CsvResultsWriterFactory implements ResultsWriterFactory { + + @Override + public ResultsWriter forUri(String uri) { + if (!uri.startsWith("csv:")) { + return null; + } + return new ResultsWriter() { + + @Override + public void write(OutputFormat output, Collection results) { + StringBuilder report = new StringBuilder(); + try { + Map params = new LinkedHashMap<>(); + int paramPlaces = 0; + for (RunResult result : results) { + for (String param : result.getParams().getParamsKeys()) { + int count = paramPlaces; + params.computeIfAbsent(param, key -> count); + paramPlaces++; + } + } + Map auxes = new LinkedHashMap<>(); + int auxPlaces = 0; + for (RunResult result : results) { + @SuppressWarnings("rawtypes") + Map second = result.getAggregatedResult() + .getSecondaryResults(); + if (second != null) { + for (String aux : second.keySet()) { + int count = auxPlaces; + auxes.computeIfAbsent(aux, key -> count); + auxPlaces++; + } + } + } + StringBuilder header = new StringBuilder(); + header.append("class, method, "); + params.forEach((key, value) -> header.append(key).append(", ")); + auxes.forEach((key, value) -> header.append(propertyName(key)) + .append(", ")); + header.append("median, mean, range"); + report.append(header.toString()).append(System.lineSeparator()); + for (RunResult result : results) { + StringBuilder builder = new StringBuilder(); + String benchmark = result.getParams().getBenchmark(); + String cls = benchmark.substring(0, benchmark.lastIndexOf(".")); + String mthd = benchmark.substring(benchmark.lastIndexOf(".") + 1); + builder.append(cls).append(", ").append(mthd).append(", "); + for (int i = 0; i < params.values().size(); i++) { + boolean found = false; + for (String param : result.getParams().getParamsKeys()) { + if (params.get(param) == i) { + builder.append(result.getParams().getParam(param)) + .append(", "); + found = true; + } + } + if (!found) { + builder.append(", "); + } + } + @SuppressWarnings("rawtypes") + Map second = result.getAggregatedResult() + .getSecondaryResults(); + if (second != null) { + for (int i = 0; i < auxes.values().size(); i++) { + boolean found = false; + for (String param : second.keySet()) { + if (auxes.get(param) == i) { + builder.append(ScoreFormatter + .format(second.get(param).getStatistics() + .getPercentile(0.5))) + .append(", "); + found = true; + } + } + if (!found) { + builder.append(", "); + } + } + } + Statistics statistics = result.getPrimaryResult().getStatistics(); + builder.append( + ScoreFormatter.format(statistics.getPercentile(0.5))); + builder.append(", "); + builder.append(ScoreFormatter.format(statistics.getMean())); + builder.append(", "); + double error = (statistics.getMax() - statistics.getMin()) / 2; + builder.append(ScoreFormatter.format(error)); + report.append(builder.toString()).append(System.lineSeparator()); + } + } + catch (Exception e) { + e.printStackTrace(); + } + output.println(report.toString()); + if (uri != null) { + File file = new File(uri.substring("csv:".length())); + file.getParentFile().mkdirs(); + if (file.getParentFile().exists()) { + try { + FileUtils.writeLines(file, + Collections.singleton(report.toString())); + } + catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + private String propertyName(String key) { + if (key.matches("get[A-Z].*")) { + key = changeFirstCharacterCase(key.substring(3), false); + } + return key; + } + }; + } + + private static String changeFirstCharacterCase(String str, boolean capitalize) { + if (str == null || str.length() == 0) { + return str; + } + + char baseChar = str.charAt(0); + char updatedChar; + if (capitalize) { + updatedChar = Character.toUpperCase(baseChar); + } + else { + updatedChar = Character.toLowerCase(baseChar); + } + if (baseChar == updatedChar) { + return str; + } + + char[] chars = str.toCharArray(); + chars[0] = updatedChar; + return new String(chars, 0, chars.length); + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherState.java b/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherState.java new file mode 100644 index 000000000..85d7c75a2 --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherState.java @@ -0,0 +1,310 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.thin.ArchiveUtils; +import org.springframework.boot.loader.thin.DependencyResolver; +import org.springframework.boot.loader.thin.PathResolver; +import org.springframework.util.ReflectionUtils; + +public class ProcessLauncherState { + + private static final Logger log = LoggerFactory.getLogger(ProcessLauncherState.class); + + public static final String CLASS_COUNT_MARKER = "Class count"; + + public static final String BEAN_COUNT_MARKER = "Bean count"; + + private Process started; + + private List args = new ArrayList<>(); + + private List progs = new ArrayList<>(); + + private static List DEFAULT_JVM_ARGS = Arrays.asList("-Xmx128m", "-cp", "", + "-Djava.security.egd=file:/dev/./urandom", "-noverify", + "-Dspring.data.jpa.repositories.bootstrap-mode=lazy", + "-Dspring.cache.type=none", "-Dspring.main.lazy-initialization=true", + "-Dspring.jmx.enabled=false"); + + private File home; + + private String mainClass; + + private String name = "thin"; + + private String[] profiles = new String[0]; + + private BufferedReader buffer; + + private CountDownLatch latch = new CountDownLatch(1); + + private int classes; + + private int beans; + + private long memory; + + private long heap; + + private String classpath; + + public int getClasses() { + return classes; + } + + public int getBeans() { + return beans; + } + + public double getMemory() { + return memory / (1024. * 1024); + } + + public double getHeap() { + return heap / (1024. * 1024); + } + + public ProcessLauncherState(String dir, String... args) { + this.args.addAll(DEFAULT_JVM_ARGS); + String vendor = System.getProperty("java.vendor", "").toLowerCase(); + if (vendor.contains("ibm") || vendor.contains("j9")) { + this.args.addAll(Arrays.asList("-Xms32m", "-Xquickstart", "-Xshareclasses", + "-Xscmx128m")); + } + else { + this.args.addAll(Arrays.asList("-XX:TieredStopAtLevel=1")); + } + if (System.getProperty("bench.args") != null) { + this.args.addAll(Arrays.asList(System.getProperty("bench.args").split(" "))); + } + this.progs.addAll(Arrays.asList(args)); + this.home = new File(dir); + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + public void setName(String name) { + this.name = name; + } + + public void setProfiles(String... profiles) { + this.profiles = profiles; + } + + public void addArgs(String... args) { + this.args.addAll(Arrays.asList(args)); + } + + protected String getClasspath() { + return getClasspath(true); + } + + protected String getClasspath(boolean includeTargetClasses) { + if (this.classpath == null) { + PathResolver resolver = new PathResolver(DependencyResolver.instance()); + Archive root = ArchiveUtils.getArchive(ProcessLauncherState.class); + List resolved = resolver.resolve(root, name, profiles); + StringBuilder builder = new StringBuilder(); + if (includeTargetClasses) { + builder.append(new File("target/classes").getAbsolutePath()); + } + else { + builder.append(new File("target/orm-0.0.1.BUILD-SNAPSHOT.jar") + .getAbsolutePath()); + } + try { + for (Archive archive : resolved) { + if (archive.getUrl().equals(root.getUrl())) { + continue; + } + if (builder.length() > 0) { + builder.append(File.pathSeparator); + } + builder.append(file(archive.getUrl().toString())); + } + } + catch (MalformedURLException e) { + throw new IllegalStateException("Cannot find archive", e); + } + log.debug("Classpath: " + builder); + this.classpath = builder.toString(); + } + return this.classpath; + } + + private String file(String path) { + if (path.endsWith("!/")) { + path = path.substring(0, path.length() - 2); + } + if (path.startsWith("jar:")) { + path = path.substring("jar:".length()); + } + if (path.startsWith("file:")) { + path = path.substring("file:".length()); + } + return path; + } + + public String getPid() { + String pid = null; + try { + if (started != null) { + Field field = ReflectionUtils.findField(started.getClass(), "pid"); + ReflectionUtils.makeAccessible(field); + pid = "" + ReflectionUtils.getField(field, started); + } + } + catch (Exception e) { + } + return pid; + } + + public void after() throws Exception { + drain(); + if (started != null && started.isAlive()) { + latch.await(10, TimeUnit.SECONDS); + Map metrics = VirtualMachineMetrics.fetch(getPid()); + this.memory = VirtualMachineMetrics.total(metrics); + this.heap = VirtualMachineMetrics.heap(metrics); + this.classes = metrics.get("Classes").intValue(); + System.out.println( + "Stopped " + mainClass + ": " + started.destroyForcibly().waitFor()); + } + } + + private BufferedReader getBuffer() { + return this.buffer; + } + + public void run() throws Exception { + List jvmArgs = new ArrayList<>(this.args); + customize(jvmArgs); + started = exec(jvmArgs.toArray(new String[0]), this.progs.toArray(new String[0])); + InputStream stream = started.getInputStream(); + this.buffer = new BufferedReader(new InputStreamReader(stream)); + monitor(); + } + + public void before() throws Exception { + int classpath = args.indexOf("-cp"); + if (classpath >= 0 && args.get(classpath + 1).length() == 0) { + args.set(classpath + 1, getClasspath()); + } + } + + protected void customize(List args) { + } + + protected Process exec(String[] jvmArgs, String... progArgs) { + List args = new ArrayList<>(Arrays.asList(jvmArgs)); + args.add(0, System.getProperty("java.home") + "/bin/java"); + if (mainClass.length() > 0) { + args.add(mainClass); + } + int classpath = args.indexOf("-cp"); + if (classpath >= 0 && args.get(classpath + 1).length() == 0) { + args.set(classpath + 1, getClasspath()); + } + args.addAll(Arrays.asList(progArgs)); + ProcessBuilder builder = new ProcessBuilder(args); + builder.redirectErrorStream(true); + builder.directory(getHome()); + if (!"false".equals(System.getProperty("debug", "false"))) { + System.out.println("Executing: " + builder.command()); + } + Process started; + try { + started = builder.start(); + return started; + } + catch (Exception e) { + e.printStackTrace(); + throw new IllegalStateException("Cannot calculate classpath"); + } + } + + protected void monitor() throws Exception { + // use this method to wait for an app to start + output(getBuffer(), StartupApplicationListener.MARKER); + } + + protected void finish() throws Exception { + // use this method to wait for an app to stop + output(getBuffer(), ShutdownApplicationListener.MARKER); + } + + protected void drain() throws Exception { + System.out.println("Draining console buffer"); + output(getBuffer(), null); + latch.countDown(); + } + + protected void output(BufferedReader br, String marker) throws Exception { + StringBuilder sb = new StringBuilder(); + String line = null; + if (!"false".equals(System.getProperty("debug", "false"))) { + System.err.println("Scanning for: " + marker); + } + while ((marker != null || br.ready()) && (line = br.readLine()) != null + && (marker == null || !line.contains(marker))) { + sb.append(line + System.getProperty("line.separator")); + if (!"false".equals(System.getProperty("debug", "false"))) { + System.out.println(line); + } + if (line.contains(CLASS_COUNT_MARKER)) { + classes = Integer + .valueOf(line.substring(line.lastIndexOf("=") + 1).trim()); + } + if (line.contains(BEAN_COUNT_MARKER)) { + int count = Integer + .valueOf(line.substring(line.lastIndexOf("=") + 1).trim()); + beans = count > beans ? count : beans; + } + line = null; + } + if (line != null) { + sb.append(line + System.getProperty("line.separator")); + } + if ("false".equals(System.getProperty("debug", "false"))) { + System.out.println(sb.toString()); + } + } + + public File getHome() { + return home; + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherStateTests.java b/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherStateTests.java new file mode 100644 index 000000000..b40484b98 --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherStateTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.main; + +import java.net.URL; + +import app.main.CaptureSystemOutput.OutputCapture; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dave Syer + * + */ +public class ProcessLauncherStateTests { + + @Test + @CaptureSystemOutput + public void vanilla(OutputCapture output) throws Exception { + // System.setProperty("bench.args", "-verbose:class"); + ProcessLauncherState state = new ProcessLauncherState("target") { + @Override + public void run() throws Exception { + super.run(); + try { + new URL("http://localhost:8080/owners").getContent(); + } + catch (Exception e) { + // ignore + } + } + }; + state.addArgs("-Dinitialize=true"); + state.setMainClass(SampleApplication.class.getName()); + // state.setProfiles("intg"); + state.before(); + state.run(); + state.after(); + assertThat(output.toString()).contains("Benchmark app started"); + assertThat(state.getHeap()).isGreaterThan(0); + assertThat(state.getClasses()).isGreaterThan(7000); + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/SampleApplicationTests.java b/samples/vanilla-orm/src/test/java/app/main/SampleApplicationTests.java new file mode 100644 index 000000000..f48c83bed --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/SampleApplicationTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.main; + +import org.junit.Before; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.server.WebHandler; + +/** + * @author Dave Syer + * + */ +@SpringBootTest +@AutoConfigureWebTestClient +public class SampleApplicationTests { + + @Autowired + private WebHandler webHandler; + + @Autowired + private WebTestClient client; + + @Before + public void init() { + client = WebTestClient.bindToWebHandler(webHandler).build(); + } + + @Test + public void test() { + client.get().uri("/").exchange().expectBody(String.class).isEqualTo("{\"value\":\"Hello\"}"); + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/SimpleBenchmark.java b/samples/vanilla-orm/src/test/java/app/main/SimpleBenchmark.java new file mode 100644 index 000000000..95a8d9a90 --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/SimpleBenchmark.java @@ -0,0 +1,133 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import java.net.URL; + +import jmh.mbr.junit5.Microbenchmark; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.AuxCounters.Type; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; + +@Measurement(iterations = 5, time = 1) +@Warmup(iterations = 1, time = 1) +@Fork(value = 2, warmups = 0) +@BenchmarkMode(Mode.AverageTime) +@Microbenchmark +public class SimpleBenchmark { + + @Benchmark + public void main(MainState state) throws Exception { + state.setMainClass(state.sample.getConfig().getName()); + state.run(); + if (state.profile.toString().startsWith("first")) { + try { + System.out.println("Loading /"); + new URL("http://localhost:8080/").getContent(); + } + catch (Exception e) { + // ignore + } + } + } + + @State(Scope.Thread) + @AuxCounters(Type.EVENTS) + public static class MainState extends ProcessLauncherState { + + public static enum Profile { + + demo, first; + + } + + public static enum Sample { + + auto; + + private Class config; + + private Sample(Class config) { + this.config = config; + } + + private Sample() { + this.config = SampleApplication.class; + } + + public Class getConfig() { + return config; + } + + } + + @Param // ("auto") + private Sample sample; + + @Param + private Profile profile; + + public MainState() { + super("target"); + } + + @Override + public int getClasses() { + return super.getClasses(); + } + + @Override + public int getBeans() { + return super.getBeans(); + } + + @Override + public double getMemory() { + return super.getMemory(); + } + + @Override + public double getHeap() { + return super.getHeap(); + } + + @TearDown(Level.Invocation) + public void stop() throws Exception { + super.after(); + } + + @Setup(Level.Trial) + public void start() throws Exception { + if (profile != Profile.demo) { + setProfiles(profile.toString()); + } + super.before(); + } + + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/VirtualMachineMetrics.java b/samples/vanilla-orm/src/test/java/app/main/VirtualMachineMetrics.java new file mode 100644 index 000000000..14000f7fb --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/VirtualMachineMetrics.java @@ -0,0 +1,198 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.management.MBeanServerConnection; +import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; + +import com.sun.tools.attach.VirtualMachine; + +/** + * @author Dave Syer + * + */ +@SuppressWarnings("restriction") +public class VirtualMachineMetrics { + + static final String CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress"; + + public static Map fetch(String pid) { + if (pid == null) { + return Collections.emptyMap(); + } + try { + VirtualMachine vm = VirtualMachine.attach(pid); + vm.startLocalManagementAgent(); + String connectorAddress = vm.getAgentProperties() + .getProperty(CONNECTOR_ADDRESS); + JMXServiceURL url = new JMXServiceURL(connectorAddress); + JMXConnector connector = JMXConnectorFactory.connect(url); + MBeanServerConnection connection = connector.getMBeanServerConnection(); + gc(connection); + Map metrics = new HashMap<>( + new BufferPools(connection).getMetrics()); + metrics.putAll(new Threads(connection).getMetrics()); + metrics.putAll(new Classes(connection).getMetrics()); + vm.detach(); + return metrics; + } + catch (Exception e) { + return Collections.emptyMap(); + } + } + + private static void gc(MBeanServerConnection mBeanServer) { + try { + final ObjectName on = new ObjectName("java.lang:type=Memory"); + mBeanServer.getMBeanInfo(on); + mBeanServer.invoke(on, "gc", new Object[0], new String[0]); + } + catch (Exception ignored) { + System.err.println("Unable to gc"); + } + } + + public static long total(Map metrics) { + return BufferPools.total(metrics); + } + + public static long heap(Map metrics) { + return BufferPools.heap(metrics); + } + +} + +class Threads { + + private final MBeanServerConnection mBeanServer; + + public Threads(MBeanServerConnection mBeanServer) { + this.mBeanServer = mBeanServer; + } + + public Map getMetrics() { + final Map gauges = new HashMap<>(); + final String name = "Threads"; + try { + final ObjectName on = new ObjectName("java.lang:type=Threading"); + mBeanServer.getMBeanInfo(on); + Integer value = (Integer) mBeanServer.getAttribute(on, "ThreadCount"); + gauges.put(name(name), Long.valueOf(value) * 1024 * 1024); + } + catch (Exception ignored) { + System.err.println("Unable to load thread pool MBeans: " + name); + } + return Collections.unmodifiableMap(gauges); + } + + private static String name(String name) { + return name.replace(" ", "-"); + } + +} + +class Classes { + + private final MBeanServerConnection mBeanServer; + + public Classes(MBeanServerConnection mBeanServer) { + this.mBeanServer = mBeanServer; + } + + public Map getMetrics() { + final Map gauges = new HashMap<>(); + final String name = "Classes"; + try { + final ObjectName on = new ObjectName("java.lang:type=ClassLoading"); + mBeanServer.getMBeanInfo(on); + Integer value = (Integer) mBeanServer.getAttribute(on, "LoadedClassCount"); + gauges.put(name(name), Long.valueOf(value)); + } + catch (Exception ignored) { + System.err.println("Unable to load thread pool MBeans: " + name); + } + return Collections.unmodifiableMap(gauges); + } + + private static String name(String name) { + return name.replace(" ", "-"); + } + +} + +class BufferPools { + + private static final String[] ATTRIBUTES = { "HeapMemoryUsage", + "NonHeapMemoryUsage" }; + + private final MBeanServerConnection mBeanServer; + + public BufferPools(MBeanServerConnection mBeanServer) { + this.mBeanServer = mBeanServer; + } + + public static long total(Map metrics) { + long total = 0; + System.err.println(metrics); + for (int i = 0; i < ATTRIBUTES.length; i++) { + final String name = name(ATTRIBUTES[i]); + total += metrics.containsKey(name) ? metrics.get(name) : 0; + } + total += metrics.getOrDefault("Threads", 0L); + return total; + } + + public static long heap(Map metrics) { + long total = 0; + for (int i = 0; i < ATTRIBUTES.length; i++) { + final String name = name(ATTRIBUTES[i]); + if (name.startsWith("Heap")) { + total += metrics.containsKey(name) ? metrics.get(name) : 0; + } + } + return total; + } + + public Map getMetrics() { + final Map gauges = new HashMap<>(); + for (int i = 0; i < ATTRIBUTES.length; i++) { + try { + final ObjectName on = new ObjectName("java.lang:type=Memory"); + final String name = ATTRIBUTES[i]; + mBeanServer.getMBeanInfo(on); + CompositeData value = (CompositeData) mBeanServer.getAttribute(on, name); + gauges.put(name(name), (Long) value.get("used")); + } + catch (Exception ignored) { + System.err.println("Unable to load memory pool MBeans"); + } + } + return Collections.unmodifiableMap(gauges); + } + + private static String name(String name) { + return name.replace(" ", "-"); + } + +} diff --git a/samples/vanilla-orm/src/test/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory b/samples/vanilla-orm/src/test/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory new file mode 100644 index 000000000..03fd95b1d --- /dev/null +++ b/samples/vanilla-orm/src/test/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory @@ -0,0 +1 @@ +app.main.CsvResultsWriterFactory \ No newline at end of file diff --git a/samples/vanilla-orm/src/test/resources/logback.xml b/samples/vanilla-orm/src/test/resources/logback.xml new file mode 100644 index 000000000..a6fe8de4a --- /dev/null +++ b/samples/vanilla-orm/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/vanilla-rabbit/compile.sh b/samples/vanilla-rabbit/compile.sh new file mode 100755 index 000000000..97966f74f --- /dev/null +++ b/samples/vanilla-rabbit/compile.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="vanilla-rabbit-0.0.1.BUILD-SNAPSHOT.jar" +rm rabbit +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP app.main.SampleApplication + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=rabbit\ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP app.main.SampleApplication + + #--debug-attach \ +mv rabbit ../../.. + +printf "\n\nCompiled app (demo)\n" +cd ../../.. +time ./rabbit + diff --git a/samples/vanilla-rabbit/pom.xml b/samples/vanilla-rabbit/pom.xml new file mode 100644 index 000000000..ffc806cd3 --- /dev/null +++ b/samples/vanilla-rabbit/pom.xml @@ -0,0 +1,125 @@ + + + 4.0.0 + + org.springframework.experimental + vanilla-rabbit + 0.0.1.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + + org.reactivestreams + reactive-streams + 1.0.3 + + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.springframework.boot.experimental + spring-boot-thin-launcher + ${thin.version} + test + + + + + 1.8 + app.main.SampleApplication + 1.21 + 1.0.22.RELEASE + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + false + + + true + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + true + + + true + + + + + diff --git a/samples/vanilla-rabbit/src/main/java/app/main/Receiver.java b/samples/vanilla-rabbit/src/main/java/app/main/Receiver.java new file mode 100644 index 000000000..1df066e74 --- /dev/null +++ b/samples/vanilla-rabbit/src/main/java/app/main/Receiver.java @@ -0,0 +1,28 @@ +package app.main; + +import app.main.model.Foo; + +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.stereotype.Component; + +@Component +public class Receiver { + + private RabbitTemplate template; + + public Receiver(RabbitTemplate template) { + this.template = template; + } + + /** + * Send a message with empty routing key, JSON content and + * content_type=application/json in the properties. + */ + @RabbitListener(queues = "#{queue.name}") + public void receive(Foo message) { + System.out.println("Received <" + message + ">"); + template.convertAndSend(new Foo(message.getValue().toUpperCase())); + } + +} \ No newline at end of file diff --git a/samples/vanilla-rabbit/src/main/java/app/main/SampleApplication.java b/samples/vanilla-rabbit/src/main/java/app/main/SampleApplication.java new file mode 100644 index 000000000..28b774f86 --- /dev/null +++ b/samples/vanilla-rabbit/src/main/java/app/main/SampleApplication.java @@ -0,0 +1,63 @@ +package app.main; + +import app.main.model.Foo; + +import org.springframework.amqp.core.AnonymousQueue; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.support.converter.ClassMapper; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication(proxyBeanMethods = false) +public class SampleApplication { + + @Bean + Queue queue() { + return new AnonymousQueue(); + } + + @Bean + TopicExchange input() { + return new TopicExchange("input"); + } + + @Bean + TopicExchange output() { + return new TopicExchange("output"); + } + + @Bean + Binding binding(Queue queue, @Qualifier("input") TopicExchange exchange) { + return BindingBuilder.bind(queue).to(exchange).with("#"); + } + + + @Bean + Jackson2JsonMessageConverter jackson2JsonMessageConverter() { + Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(); + converter.setClassMapper(new ClassMapper() { + + @Override + public Class toClass(MessageProperties properties) { + return Foo.class; + } + + @Override + public void fromClass(Class clazz, MessageProperties properties) { + } + }); + return converter; + } + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} diff --git a/samples/vanilla-rabbit/src/main/java/app/main/model/Foo.java b/samples/vanilla-rabbit/src/main/java/app/main/model/Foo.java new file mode 100644 index 000000000..cefb6171e --- /dev/null +++ b/samples/vanilla-rabbit/src/main/java/app/main/model/Foo.java @@ -0,0 +1,23 @@ +package app.main.model; + +public class Foo { + private long id; + private String value; + public Foo() { + } + public Foo(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return String.format( + "Foo[id=%d, value='%s']", + id, value); + } +} diff --git a/samples/vanilla-rabbit/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-rabbit/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..1ae607c17 --- /dev/null +++ b/samples/vanilla-rabbit/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,12 @@ +[ + { + "name": "app.main.model.Foo", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "app.main.Receiver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] \ No newline at end of file diff --git a/samples/vanilla-rabbit/src/main/resources/application.properties b/samples/vanilla-rabbit/src/main/resources/application.properties new file mode 100644 index 000000000..ca58d9007 --- /dev/null +++ b/samples/vanilla-rabbit/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.rabbitmq.template.exchange=output +spring.rabbitmq.template.routingKey= \ No newline at end of file diff --git a/samples/vanilla-rabbit/src/test/java/app/main/ClientApplication.java b/samples/vanilla-rabbit/src/test/java/app/main/ClientApplication.java new file mode 100644 index 000000000..b5c8d5464 --- /dev/null +++ b/samples/vanilla-rabbit/src/test/java/app/main/ClientApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import com.rabbitmq.client.impl.ClientVersion; + +/** + * @author Dave Syer + * + */ +public class ClientApplication { + + public static void main(String[] args) { + System.err.println(ClientVersion.VERSION); + } +} diff --git a/samples/vanilla-rabbit/src/test/java/app/main/SampleApplicationTests.java b/samples/vanilla-rabbit/src/test/java/app/main/SampleApplicationTests.java new file mode 100644 index 000000000..3f9fe5272 --- /dev/null +++ b/samples/vanilla-rabbit/src/test/java/app/main/SampleApplicationTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.main; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Dave Syer + * + */ +@SpringBootTest +@AutoConfigureWebTestClient +public class SampleApplicationTests { + + @Test + public void test() { + } + +} diff --git a/samples/vanilla-rabbit/src/test/resources/logback.xml b/samples/vanilla-rabbit/src/test/resources/logback.xml new file mode 100644 index 000000000..a6fe8de4a --- /dev/null +++ b/samples/vanilla-rabbit/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.jar b/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..5fd4d5023 Binary files /dev/null and b/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.properties b/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..c954cec91 --- /dev/null +++ b/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip diff --git a/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.jar b/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..5fd4d5023 Binary files /dev/null and b/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.properties b/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..c954cec91 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip diff --git a/samples/vanilla-thymeleaf/bin/compile.sh b/samples/vanilla-thymeleaf/bin/compile.sh new file mode 100755 index 000000000..05ccd9d67 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/compile.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="vanilla-thymeleaf-0.1.0.jar" +rm thymeleaf +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP hello.Application + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-boot-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=thymeleaf \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -H:+TraceClassInitialization=io.netty.bootstrap.AbstractBootstrap \ + -DremoveUnusedAutoconfig=true \ + -cp $CP hello.Application + + #--debug-attach \ +mv thymeleaf ../../.. + +printf "\n\nCompiled app (demo)\n" +cd ../../.. +./thymeleaf + diff --git a/samples/vanilla-thymeleaf/bin/mvnw b/samples/vanilla-thymeleaf/bin/mvnw new file mode 100755 index 000000000..a1ba1bf55 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + 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 Migwn, 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)`" + # TODO classpath? +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 + +# 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"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# 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} "$@" diff --git a/samples/vanilla-thymeleaf/bin/mvnw.cmd b/samples/vanilla-thymeleaf/bin/mvnw.cmd new file mode 100755 index 000000000..2b934e89d --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my 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 + +set MAVEN_CMD_LINE_ARGS=%* + +@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="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +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% \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/bin/pom.xml b/samples/vanilla-thymeleaf/bin/pom.xml new file mode 100644 index 000000000..22d9f88bc --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + com.example + vanilla-thymeleaf + 0.1.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.BUILD-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-test + test + + + + + 1.8 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + diff --git a/samples/vanilla-thymeleaf/bin/src/main/java/hello/Application.class b/samples/vanilla-thymeleaf/bin/src/main/java/hello/Application.class new file mode 100644 index 000000000..d794ab0b1 Binary files /dev/null and b/samples/vanilla-thymeleaf/bin/src/main/java/hello/Application.class differ diff --git a/samples/vanilla-thymeleaf/bin/src/main/java/hello/Greeting.class b/samples/vanilla-thymeleaf/bin/src/main/java/hello/Greeting.class new file mode 100644 index 000000000..6a282571c Binary files /dev/null and b/samples/vanilla-thymeleaf/bin/src/main/java/hello/Greeting.class differ diff --git a/samples/vanilla-thymeleaf/bin/src/main/java/hello/GreetingController.class b/samples/vanilla-thymeleaf/bin/src/main/java/hello/GreetingController.class new file mode 100644 index 000000000..131d664ac Binary files /dev/null and b/samples/vanilla-thymeleaf/bin/src/main/java/hello/GreetingController.class differ diff --git a/samples/vanilla-thymeleaf/bin/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-thymeleaf/bin/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..378693672 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,7 @@ +[ + { + "name": "hello.Greeting", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/bin/src/main/resources/static/index.html b/samples/vanilla-thymeleaf/bin/src/main/resources/static/index.html new file mode 100644 index 000000000..33e9b7e90 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + Getting Started: Serving Web Content + + + +

Get your greeting here

+ + diff --git a/samples/vanilla-thymeleaf/bin/src/main/resources/templates/greeting.html b/samples/vanilla-thymeleaf/bin/src/main/resources/templates/greeting.html new file mode 100644 index 000000000..b4c497975 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/src/main/resources/templates/greeting.html @@ -0,0 +1,10 @@ + + + + Getting Started: Serving Web Content + + + +

+ + diff --git a/samples/vanilla-thymeleaf/bin/src/test/java/hello/ApplicationTests.class b/samples/vanilla-thymeleaf/bin/src/test/java/hello/ApplicationTests.class new file mode 100644 index 000000000..318271e18 Binary files /dev/null and b/samples/vanilla-thymeleaf/bin/src/test/java/hello/ApplicationTests.class differ diff --git a/samples/vanilla-thymeleaf/compile.sh b/samples/vanilla-thymeleaf/compile.sh new file mode 100755 index 000000000..519d3dcc0 --- /dev/null +++ b/samples/vanilla-thymeleaf/compile.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="vanilla-thymeleaf-0.1.0.jar" +rm thymeleaf +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP hello.Application + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=thymeleaf \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP hello.Application + + + #--debug-attach \ +mv thymeleaf ../../.. + +printf "\n\nCompiled app (demo)\n" +cd ../../.. +./thymeleaf + diff --git a/samples/vanilla-thymeleaf/mvnw b/samples/vanilla-thymeleaf/mvnw new file mode 100755 index 000000000..a1ba1bf55 --- /dev/null +++ b/samples/vanilla-thymeleaf/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + 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 Migwn, 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)`" + # TODO classpath? +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 + +# 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"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# 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} "$@" diff --git a/samples/vanilla-thymeleaf/mvnw.cmd b/samples/vanilla-thymeleaf/mvnw.cmd new file mode 100755 index 000000000..2b934e89d --- /dev/null +++ b/samples/vanilla-thymeleaf/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my 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 + +set MAVEN_CMD_LINE_ARGS=%* + +@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="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +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% \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/pom.xml b/samples/vanilla-thymeleaf/pom.xml new file mode 100644 index 000000000..aabed5eae --- /dev/null +++ b/samples/vanilla-thymeleaf/pom.xml @@ -0,0 +1,138 @@ + + + 4.0.0 + + com.example + vanilla-thymeleaf + 0.1.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-logging + + + org.hibernate.validator + hibernate-validator + + + io.netty + netty-transport-native-epoll + + + io.netty + netty-codec-http2 + + + jakarta.validation + jakarta.validation-api + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-test + test + + + + + 1.8 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + diff --git a/samples/vanilla-thymeleaf/src/main/java/hello/Application.java b/samples/vanilla-thymeleaf/src/main/java/hello/Application.java new file mode 100644 index 000000000..59428955b --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/java/hello/Application.java @@ -0,0 +1,13 @@ +package hello; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(proxyBeanMethods = false) +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/samples/vanilla-thymeleaf/src/main/java/hello/GreetingController.java b/samples/vanilla-thymeleaf/src/main/java/hello/GreetingController.java new file mode 100644 index 000000000..bcf1ffc66 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/java/hello/GreetingController.java @@ -0,0 +1,54 @@ +package hello; + +import java.util.UUID; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class GreetingController { + + @GetMapping("/greeting") + public String greeting( + @RequestParam(name = "name", required = false, defaultValue = "World") String name, + Model model) { + model.addAttribute("greeting", new Greeting(name)); + return "greeting"; + } + +} + +class Greeting { + + private String id = UUID.randomUUID().toString(); + + private String msg; + + @SuppressWarnings("unused") + private Greeting() { + } + + public Greeting(String msg) { + this.msg = msg; + } + + public String getId() { + return id; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + @Override + public String toString() { + return "Greeting [msg=" + msg + "]"; + } + +} \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-thymeleaf/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..378693672 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,7 @@ +[ + { + "name": "hello.Greeting", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/src/main/resources/static/index.html b/samples/vanilla-thymeleaf/src/main/resources/static/index.html new file mode 100644 index 000000000..33e9b7e90 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + Getting Started: Serving Web Content + + + +

Get your greeting here

+ + diff --git a/samples/vanilla-thymeleaf/src/main/resources/templates/greeting.html b/samples/vanilla-thymeleaf/src/main/resources/templates/greeting.html new file mode 100644 index 000000000..b4c497975 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/resources/templates/greeting.html @@ -0,0 +1,10 @@ + + + + Getting Started: Serving Web Content + + + +

+ + diff --git a/samples/vanilla-thymeleaf/src/test/java/hello/ApplicationTests.java b/samples/vanilla-thymeleaf/src/test/java/hello/ApplicationTests.java new file mode 100644 index 000000000..f69292552 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/test/java/hello/ApplicationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hello; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +@RunWith(SpringRunner.class) +@WebFluxTest(controllers = GreetingController.class) +public class ApplicationTests { + + @Autowired + private WebTestClient client; + + @Test + public void homePage() throws Exception { + // N.B. jsoup can be useful for asserting HTML content + client.get().uri("/index.html").exchange().expectBody(String.class).consumeWith( + content -> content.getResponseBody().contains("Get your greeting")); + } + + @Test + public void greeting() throws Exception { + client.get().uri("/greeting").exchange().expectBody(String.class).consumeWith( + content -> content.getResponseBody().contains("Hello, World!")); + } + + @Test + public void greetingWithUser() throws Exception { + client.get().uri("/greeting?name={name}", "Greg").exchange() + .expectBody(String.class).consumeWith( + content -> content.getResponseBody().contains("Hello, Greg!")); + } + +} diff --git a/samples/vanilla-tx/compile.sh b/samples/vanilla-tx/compile.sh new file mode 100755 index 000000000..0a2253ae2 --- /dev/null +++ b/samples/vanilla-tx/compile.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="vanilla-tx-0.0.1.BUILD-SNAPSHOT.jar" +rm tx +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP hello.Application + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=tx \ + -H:+ReportExceptionStackTraces \ + -H:+TraceClassInitialization \ + --no-fallback \ + --allow-incomplete-classpath \ + -H:EnableURLProtocols=https \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP app.main.SampleApplication + + #--debug-attach \ +mv tx ../../.. + +printf "\n\nCompiled app \n" +cd ../../.. +./tx + diff --git a/samples/vanilla-tx/pom.xml b/samples/vanilla-tx/pom.xml new file mode 100644 index 000000000..62624d669 --- /dev/null +++ b/samples/vanilla-tx/pom.xml @@ -0,0 +1,133 @@ + + + 4.0.0 + + org.springframework.experimental + vanilla-tx + 0.0.1.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-webflux + + + io.netty + netty-transport-native-epoll + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + org.hibernate.validator + hibernate-validator + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + 1.8 + app.main.SampleApplication + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + false + + + true + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + true + + + true + + + + + diff --git a/samples/vanilla-tx/src/main/java/app/main/Finder.java b/samples/vanilla-tx/src/main/java/app/main/Finder.java new file mode 100644 index 000000000..d37fa2f8d --- /dev/null +++ b/samples/vanilla-tx/src/main/java/app/main/Finder.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +/** + * @author Dave Syer + * + */ +public interface Finder { + + T find(long id); + +} diff --git a/samples/vanilla-tx/src/main/java/app/main/Runner.java b/samples/vanilla-tx/src/main/java/app/main/Runner.java new file mode 100644 index 000000000..4c61577bd --- /dev/null +++ b/samples/vanilla-tx/src/main/java/app/main/Runner.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.main; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import app.main.model.Foo; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +/** + * @author Dave Syer + */ +@Component +public class Runner implements CommandLineRunner, Finder { + + private static final String GET_FOO = "SELECT VALUE from FOOS where ID=?"; + + private static final String ADD_FOO = "INSERT into FOOS (ID, VALUE) values (?, ?)"; + + private final JdbcTemplate entities; + + private final FooMapper mapper = new FooMapper(); + + public Runner(JdbcTemplate entities) { + this.entities = entities; + } + + @Override + @Transactional + public void run(String... args) throws Exception { + Assert.isTrue(TransactionSynchronizationManager.isActualTransactionActive(), "Expected transaction"); + try { + find(1L); + } + catch (EmptyResultDataAccessException e) { + entities.update(ADD_FOO, 1L, "Hello"); + } + } + + class FooMapper implements RowMapper { + + @Override + public Foo mapRow(ResultSet rs, int rowNum) throws SQLException { + return new Foo(rs.getString(1)); + } + + } + + @Override + public Foo find(long id) { + return entities.queryForObject(GET_FOO, mapper, id); + } + +} \ No newline at end of file diff --git a/samples/vanilla-tx/src/main/java/app/main/SampleApplication.java b/samples/vanilla-tx/src/main/java/app/main/SampleApplication.java new file mode 100644 index 000000000..af8b15e86 --- /dev/null +++ b/samples/vanilla-tx/src/main/java/app/main/SampleApplication.java @@ -0,0 +1,29 @@ +package app.main; + +import app.main.model.Foo; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.server.RouterFunction; + +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@SpringBootApplication(proxyBeanMethods = false) +public class SampleApplication { + + @Bean + public RouterFunction userEndpoints(Finder entities) { + return route(GET("/"), request -> ok() + .body(Mono.fromCallable(() -> entities.find(1L)).subscribeOn(Schedulers.elastic()), Foo.class)); + } + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} diff --git a/samples/vanilla-tx/src/main/java/app/main/model/Foo.java b/samples/vanilla-tx/src/main/java/app/main/model/Foo.java new file mode 100644 index 000000000..2bea6ba42 --- /dev/null +++ b/samples/vanilla-tx/src/main/java/app/main/model/Foo.java @@ -0,0 +1,27 @@ +package app.main.model; + +public class Foo { + + private String value; + + public Foo() { + } + + public Foo(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return String.format("Foo[value='%s']", value); + } + +} diff --git a/samples/vanilla-tx/src/main/resources/application.properties b/samples/vanilla-tx/src/main/resources/application.properties new file mode 100644 index 000000000..f64911866 --- /dev/null +++ b/samples/vanilla-tx/src/main/resources/application.properties @@ -0,0 +1,2 @@ +logging.level.slim=debug +spring.aop.proxy-target-class=false \ No newline at end of file diff --git a/samples/vanilla-tx/src/main/resources/schema.sql b/samples/vanilla-tx/src/main/resources/schema.sql new file mode 100644 index 000000000..88e3db1fb --- /dev/null +++ b/samples/vanilla-tx/src/main/resources/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE FOOS ( + id INTEGER IDENTITY PRIMARY KEY, + value VARCHAR(30) +); \ No newline at end of file diff --git a/samples/vanilla-tx/src/test/java/app/main/SampleApplicationTests.java b/samples/vanilla-tx/src/test/java/app/main/SampleApplicationTests.java new file mode 100644 index 000000000..01a4ab0fa --- /dev/null +++ b/samples/vanilla-tx/src/test/java/app/main/SampleApplicationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.main; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author Dave Syer + * + */ +@SpringBootTest +@AutoConfigureWebTestClient +public class SampleApplicationTests { + + @Autowired + private WebTestClient client; + + @Test + public void test() { + client.get().uri("/").exchange().expectBody(String.class).isEqualTo("{\"value\":\"Hello\"}"); + } + +} diff --git a/samples/vanilla-tx/src/test/resources/logback.xml b/samples/vanilla-tx/src/test/resources/logback.xml new file mode 100644 index 000000000..a6fe8de4a --- /dev/null +++ b/samples/vanilla-tx/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webflux-netty/.mvn/wrapper/MavenWrapperDownloader.java b/samples/webflux-netty/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 000000000..72308aa47 --- /dev/null +++ b/samples/webflux-netty/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,114 @@ +/* +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. +*/ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: : " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/samples/webflux-netty/.mvn/wrapper/maven-wrapper.jar b/samples/webflux-netty/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..01e679973 Binary files /dev/null and b/samples/webflux-netty/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/webflux-netty/.mvn/wrapper/maven-wrapper.properties b/samples/webflux-netty/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..cd0d451cc --- /dev/null +++ b/samples/webflux-netty/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip diff --git a/samples/webflux-netty/compile.sh b/samples/webflux-netty/compile.sh new file mode 100755 index 000000000..203691465 --- /dev/null +++ b/samples/webflux-netty/compile.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +../../mvnw clean install + +export JAR="webflux-netty-0.0.1-SNAPSHOT.jar" +rm webflux-netty +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP com.example.demo.DemoApplication + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:+TraceClassInitialization \ + -H:Name=webflux-netty \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -cp $CP com.example.demo.DemoApplication + +# -DremoveUnusedAutoconfig=true \ +mv webflux-netty ../../.. + +printf "\n\nCompiled app (webflux-netty)\n" +cd ../../.. +time ./webflux-netty + diff --git a/samples/webflux-netty/mvnw b/samples/webflux-netty/mvnw new file mode 100644 index 000000000..8b9da3b8b --- /dev/null +++ b/samples/webflux-netty/mvnw @@ -0,0 +1,286 @@ +#!/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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +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 + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + 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 command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + 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 + +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/samples/webflux-netty/mvnw.cmd b/samples/webflux-netty/mvnw.cmd new file mode 100644 index 000000000..fef5a8f7f --- /dev/null +++ b/samples/webflux-netty/mvnw.cmd @@ -0,0 +1,161 @@ +@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 Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my 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.4.2/maven-wrapper-0.4.2.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% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%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/samples/webflux-netty/pom.xml b/samples/webflux-netty/pom.xml new file mode 100644 index 000000000..68f7e0ef3 --- /dev/null +++ b/samples/webflux-netty/pom.xml @@ -0,0 +1,133 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + com.example + webflux-netty + 0.0.1-SNAPSHOT + demo + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.hibernate.validator + hibernate-validator + + + io.netty + netty-transport-native-epoll + + + io.netty + netty-codec-http2 + + + jakarta.validation + jakarta.validation-api + + + + + org.springframework + spring-context-indexer + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + diff --git a/samples/webflux-netty/src/main/java/com/example/demo/DemoApplication.java b/samples/webflux-netty/src/main/java/com/example/demo/DemoApplication.java new file mode 100644 index 000000000..1db6e609b --- /dev/null +++ b/samples/webflux-netty/src/main/java/com/example/demo/DemoApplication.java @@ -0,0 +1,28 @@ +package com.example.demo; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication(proxyBeanMethods = false) +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + + @RestController + class Foo { + + @GetMapping("/") + public String greet() { + return "hi!"; + } + } + +} diff --git a/samples/webflux-netty/src/main/java/com/example/demo/Foobar.java b/samples/webflux-netty/src/main/java/com/example/demo/Foobar.java new file mode 100644 index 000000000..251fb8335 --- /dev/null +++ b/samples/webflux-netty/src/main/java/com/example/demo/Foobar.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class Foobar { + + @GetMapping("/x") + public String greet2() { + return "hix!"; + } +} \ No newline at end of file diff --git a/samples/webflux-netty/src/main/resources/application.properties b/samples/webflux-netty/src/main/resources/application.properties new file mode 100644 index 000000000..e69de29bb diff --git a/samples/webflux-netty/src/main/resources/logging.properties b/samples/webflux-netty/src/main/resources/logging.properties new file mode 100644 index 000000000..70a31b86a --- /dev/null +++ b/samples/webflux-netty/src/main/resources/logging.properties @@ -0,0 +1,12 @@ +handlers =java.util.logging.ConsoleHandler +.level = INFO + +java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter +java.util.logging.ConsoleHandler.level = ALL + +org.hibernate.validator.internal.util.Version.level = WARNING +org.apache.coyote.http11.Http11NioProtocol.level = WARNING +org.apache.tomcat.util.net.NioSelectorPool.level = WARNING +org.apache.catalina.startup.DigesterFactory.level = SEVERE +org.apache.catalina.util.LifecycleBase.level = SEVERE +org.eclipse.jetty.util.component.AbstractLifeCycle.level = SEVERE diff --git a/samples/webflux-netty/src/test/java/com/example/demo/DemoApplicationTests.java b/samples/webflux-netty/src/test/java/com/example/demo/DemoApplicationTests.java new file mode 100644 index 000000000..b4c8f5df3 --- /dev/null +++ b/samples/webflux-netty/src/test/java/com/example/demo/DemoApplicationTests.java @@ -0,0 +1,16 @@ +//package com.example.demo; +// +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.test.context.junit4.SpringRunner; +// +//@RunWith(SpringRunner.class) +//@SpringBootTest +//public class DemoApplicationTests { +// +// @Test +// public void contextLoads() { +// } +// +//} diff --git a/src/json-shade/README.adoc b/src/json-shade/README.adoc new file mode 100644 index 000000000..654800694 --- /dev/null +++ b/src/json-shade/README.adoc @@ -0,0 +1,5 @@ +## Shaded JSON + +This source was originally taken from `com.vaadin.external.google:android-json` which +provides a clean room re-implementation of the `org.json` APIs and does not include the +"Do not use for evil" clause. diff --git a/src/json-shade/java/org/springframework/graal/json/JSON.java b/src/json-shade/java/org/springframework/graal/json/JSON.java new file mode 100644 index 000000000..034179dfc --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSON.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.json; + +class JSON { + + static double checkDouble(double d) throws JSONException { + if (Double.isInfinite(d) || Double.isNaN(d)) { + throw new JSONException("Forbidden numeric value: " + d); + } + return d; + } + + static Boolean toBoolean(Object value) { + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof String) { + String stringValue = (String) value; + if ("true".equalsIgnoreCase(stringValue)) { + return true; + } + if ("false".equalsIgnoreCase(stringValue)) { + return false; + } + } + return null; + } + + static Double toDouble(Object value) { + if (value instanceof Double) { + return (Double) value; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + if (value instanceof String) { + try { + return Double.valueOf((String) value); + } + catch (NumberFormatException ignored) { + } + } + return null; + } + + static Integer toInteger(Object value) { + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + if (value instanceof String) { + try { + return (int) Double.parseDouble((String) value); + } + catch (NumberFormatException ignored) { + } + } + return null; + } + + static Long toLong(Object value) { + if (value instanceof Long) { + return (Long) value; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + if (value instanceof String) { + try { + return (long) Double.parseDouble((String) value); + } + catch (NumberFormatException ignored) { + } + } + return null; + } + + static String toString(Object value) { + if (value instanceof String) { + return (String) value; + } + if (value != null) { + return String.valueOf(value); + } + return null; + } + + public static JSONException typeMismatch(Object indexOrName, Object actual, + String requiredType) throws JSONException { + if (actual == null) { + throw new JSONException("Value at " + indexOrName + " is null."); + } + throw new JSONException("Value " + actual + " at " + indexOrName + " of type " + + actual.getClass().getName() + " cannot be converted to " + + requiredType); + } + + public static JSONException typeMismatch(Object actual, String requiredType) + throws JSONException { + if (actual == null) { + throw new JSONException("Value is null."); + } + throw new JSONException( + "Value " + actual + " of type " + actual.getClass().getName() + + " cannot be converted to " + requiredType); + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONArray.java b/src/json-shade/java/org/springframework/graal/json/JSONArray.java new file mode 100644 index 000000000..300644e92 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONArray.java @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.json; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * A dense indexed sequence of values. Values may be any mix of {@link JSONObject + * JSONObjects}, other {@link JSONArray JSONArrays}, Strings, Booleans, Integers, Longs, + * Doubles, {@code null} or {@link JSONObject#NULL}. Values may not be + * {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not + * listed here. + *

+ * {@code JSONArray} has the same type coercion behavior and optional/mandatory accessors + * as {@link JSONObject}. See that class' documentation for details. + *

+ * Warning: this class represents null in two incompatible ways: the + * standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}. + * In particular, {@code get} fails if the requested index holds the null reference, but + * succeeds if it holds {@code JSONObject.NULL}. + *

+ * Instances of this class are not thread safe. Although this class is nonfinal, it was + * not designed for inheritance and should not be subclassed. In particular, self-use by + * overridable methods is not specified. See Effective Java Item 17, "Design and + * Document or inheritance or else prohibit it" for further information. + */ +public class JSONArray { + + private final List values; + + /** + * Creates a {@code JSONArray} with no values. + */ + public JSONArray() { + this.values = new ArrayList<>(); + } + + /** + * Creates a new {@code JSONArray} by copying all values from the given collection. + * @param copyFrom a collection whose values are of supported types. Unsupported + * values are not permitted and will yield an array in an inconsistent state. + */ + /* Accept a raw type for API compatibility */ + @SuppressWarnings("rawtypes") + public JSONArray(Collection copyFrom) { + this(); + if (copyFrom != null) { + for (Iterator it = copyFrom.iterator(); it.hasNext();) { + put(JSONObject.wrap(it.next())); + } + } + } + + /** + * Creates a new {@code JSONArray} with values from the next array in the tokener. + * @param readFrom a tokener whose nextValue() method will yield a {@code JSONArray}. + * @throws JSONException if the parse fails or doesn't yield a {@code JSONArray}. + * @throws JSONException if processing of json failed + */ + public JSONArray(JSONTokener readFrom) throws JSONException { + /* + * Getting the parser to populate this could get tricky. Instead, just parse to + * temporary JSONArray and then steal the data from that. + */ + Object object = readFrom.nextValue(); + if (object instanceof JSONArray) { + this.values = ((JSONArray) object).values; + } + else { + throw JSON.typeMismatch(object, "JSONArray"); + } + } + + /** + * Creates a new {@code JSONArray} with values from the JSON string. + * @param json a JSON-encoded string containing an array. + * @throws JSONException if the parse fails or doesn't yield a {@code + * JSONArray}. + */ + public JSONArray(String json) throws JSONException { + this(new JSONTokener(json)); + } + + /** + * Creates a new {@code JSONArray} with values from the given primitive array. + * @param array a primitive array + * @throws JSONException if processing of json failed + */ + public JSONArray(Object array) throws JSONException { + if (!array.getClass().isArray()) { + throw new JSONException("Not a primitive array: " + array.getClass()); + } + final int length = Array.getLength(array); + this.values = new ArrayList<>(length); + for (int i = 0; i < length; ++i) { + put(JSONObject.wrap(Array.get(array, i))); + } + } + + /** + * Returns the number of values in this array. + * @return the length of this array + */ + public int length() { + return this.values.size(); + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value the value + * @return this array. + */ + public JSONArray put(boolean value) { + this.values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(double value) throws JSONException { + this.values.add(JSON.checkDouble(value)); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * @param value the value + * @return this array. + */ + public JSONArray put(int value) { + this.values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * @param value the value + * @return this array. + */ + public JSONArray put(long value) { + this.values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. Unsupported + * values are not permitted and will cause the array to be in an inconsistent state. + * @return this array. + */ + public JSONArray put(Object value) { + this.values.add(value); + return this; + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value the value + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, boolean value) throws JSONException { + return put(index, (Boolean) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, double value) throws JSONException { + return put(index, (Double) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value the value + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, int value) throws JSONException { + return put(index, (Integer) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value the value + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, long value) throws JSONException { + return put(index, (Long) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, Object value) throws JSONException { + if (value instanceof Number) { + // deviate from the original by checking all Numbers, not just floats & + // doubles + JSON.checkDouble(((Number) value).doubleValue()); + } + while (this.values.size() <= index) { + this.values.add(null); + } + this.values.set(index, value); + return this; + } + + /** + * Returns true if this array has no value at {@code index}, or if its value is the + * {@code null} reference or {@link JSONObject#NULL}. + * @param index the index to set the value to + * @return true if this array has no value at {@code index} + */ + public boolean isNull(int index) { + Object value = opt(index); + return value == null || value == JSONObject.NULL; + } + + /** + * Returns the value at {@code index}. + * @param index the index to get the value from + * @return the value at {@code index}. + * @throws JSONException if this array has no value at {@code index}, or if that value + * is the {@code null} reference. This method returns normally if the value is + * {@code JSONObject#NULL}. + */ + public Object get(int index) throws JSONException { + try { + Object value = this.values.get(index); + if (value == null) { + throw new JSONException("Value at " + index + " is null."); + } + return value; + } + catch (IndexOutOfBoundsException e) { + throw new JSONException( + "Index " + index + " out of range [0.." + this.values.size() + ")"); + } + } + + /** + * Returns the value at {@code index}, or null if the array has no value at + * {@code index}. + * @param index the index to get the value from + * @return the value at {@code index} or {@code null} + */ + public Object opt(int index) { + if (index < 0 || index >= this.values.size()) { + return null; + } + return this.values.get(index); + } + + /** + * Removes and returns the value at {@code index}, or null if the array has no value + * at {@code index}. + * @param index the index of the value to remove + * @return the previous value at {@code index} + */ + public Object remove(int index) { + if (index < 0 || index >= this.values.size()) { + return null; + } + return this.values.remove(index); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can be coerced + * to a boolean. + * @param index the index to get the value from + * @return the value at {@code index} + * @throws JSONException if the value at {@code index} doesn't exist or cannot be + * coerced to a boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = get(index); + Boolean result = JSON.toBoolean(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "boolean"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can be coerced + * to a boolean. Returns false otherwise. + * @param index the index to get the value from + * @return the {@code value} or {@code false} + */ + public boolean optBoolean(int index) { + return optBoolean(index, false); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can be coerced + * to a boolean. Returns {@code fallback} otherwise. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public boolean optBoolean(int index, boolean fallback) { + Object object = opt(index); + Boolean result = JSON.toBoolean(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be coerced + * to a double. + * @param index the index to get the value from + * @return the {@code value} + * @throws JSONException if the value at {@code index} doesn't exist or cannot be + * coerced to a double. + */ + public double getDouble(int index) throws JSONException { + Object object = get(index); + Double result = JSON.toDouble(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "double"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be coerced + * to a double. Returns {@code NaN} otherwise. + * @param index the index to get the value from + * @return the {@code value} or {@code NaN} + */ + public double optDouble(int index) { + return optDouble(index, Double.NaN); + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be coerced + * to a double. Returns {@code fallback} otherwise. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public double optDouble(int index, double fallback) { + Object object = opt(index); + Double result = JSON.toDouble(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be coerced to + * an int. + * @param index the index to get the value from + * @return the {@code value} + * @throws JSONException if the value at {@code index} doesn't exist or cannot be + * coerced to an int. + */ + public int getInt(int index) throws JSONException { + Object object = get(index); + Integer result = JSON.toInteger(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "int"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be coerced to + * an int. Returns 0 otherwise. + * @param index the index to get the value from + * @return the {@code value} or {@code 0} + */ + public int optInt(int index) { + return optInt(index, 0); + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be coerced to + * an int. Returns {@code fallback} otherwise. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public int optInt(int index, int fallback) { + Object object = opt(index); + Integer result = JSON.toInteger(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be coerced to + * a long. + * @param index the index to get the value from + * @return the {@code value} + * + * @throws JSONException if the value at {@code index} doesn't exist or cannot be + * coerced to a long. + */ + public long getLong(int index) throws JSONException { + Object object = get(index); + Long result = JSON.toLong(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "long"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be coerced to + * a long. Returns 0 otherwise. + * @param index the index to get the value from + * @return the {@code value} or {@code 0} + */ + public long optLong(int index) { + return optLong(index, 0L); + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be coerced to + * a long. Returns {@code fallback} otherwise. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public long optLong(int index, long fallback) { + Object object = opt(index); + Long result = JSON.toLong(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists, coercing it if necessary. + * @param index the index to get the value from + * @return the {@code value} + * @throws JSONException if no such value exists. + */ + public String getString(int index) throws JSONException { + Object object = get(index); + String result = JSON.toString(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "String"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists, coercing it if necessary. Returns + * the empty string if no such value exists. + * @param index the index to get the value from + * @return the {@code value} or an empty string + */ + public String optString(int index) { + return optString(index, ""); + } + + /** + * Returns the value at {@code index} if it exists, coercing it if necessary. Returns + * {@code fallback} if no such value exists. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public String optString(int index, String fallback) { + Object object = opt(index); + String result = JSON.toString(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONArray}. + * @param index the index to get the value from + * @return the array at {@code index} + * @throws JSONException if the value doesn't exist or is not a {@code + * JSONArray}. + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + else { + throw JSON.typeMismatch(index, object, "JSONArray"); + } + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONArray}. Returns null otherwise. + * @param index the index to get the value from + * @return the array at {@code index} or {@code null} + */ + public JSONArray optJSONArray(int index) { + Object object = opt(index); + return object instanceof JSONArray ? (JSONArray) object : null; + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONObject}. + * @param index the index to get the value from + * @return the object at {@code index} + * @throws JSONException if the value doesn't exist or is not a {@code + * JSONObject}. + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + else { + throw JSON.typeMismatch(index, object, "JSONObject"); + } + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONObject}. Returns null otherwise. + * @param index the index to get the value from + * @return the object at {@code index} or {@code null} + */ + public JSONObject optJSONObject(int index) { + Object object = opt(index); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Returns a new object whose values are the values in this array, and whose names are + * the values in {@code names}. Names and values are paired up by index from 0 through + * to the shorter array's length. Names that are not strings will be coerced to + * strings. This method returns null if either array is empty. + * @param names the property names + * @return a json object + * @throws JSONException if processing of json failed + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + JSONObject result = new JSONObject(); + int length = Math.min(names.length(), this.values.size()); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = JSON.toString(names.opt(i)); + result.put(name, opt(i)); + } + return result; + } + + /** + * Returns a new string by alternating this array's values with {@code + * separator}. This array's string values are quoted and have their special characters + * escaped. For example, the array containing the strings '12" pizza', 'taco' and + * 'soda' joined on '+' returns this:
"12\" pizza"+"taco"+"soda"
+ * @param separator the separator to use + * @return the joined value + * @throws JSONException if processing of json failed + */ + public String join(String separator) throws JSONException { + JSONStringer stringer = new JSONStringer(); + stringer.open(JSONStringer.Scope.NULL, ""); + for (int i = 0, size = this.values.size(); i < size; i++) { + if (i > 0) { + stringer.out.append(separator); + } + stringer.value(this.values.get(i)); + } + stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); + return stringer.out.toString(); + } + + /** + * Encodes this array as a compact JSON string, such as:
[94043,90210]
+ * @return a compact JSON string representation of this array + */ + @Override + public String toString() { + try { + JSONStringer stringer = new JSONStringer(); + writeTo(stringer); + return stringer.toString(); + } + catch (JSONException e) { + return null; + } + } + + /** + * Encodes this array as a human readable JSON string for debugging, such as:
+	 * [
+	 *     94043,
+	 *     90210
+	 * ]
+ * + * @param indentSpaces the number of spaces to indent for each level of nesting. + * @return a human readable JSON string of this array + * @throws JSONException if processing of json failed + */ + public String toString(int indentSpaces) throws JSONException { + JSONStringer stringer = new JSONStringer(indentSpaces); + writeTo(stringer); + return stringer.toString(); + } + + void writeTo(JSONStringer stringer) throws JSONException { + stringer.array(); + for (Object value : this.values) { + stringer.value(value); + } + stringer.endArray(); + } + + @Override + public boolean equals(Object o) { + return o instanceof JSONArray && ((JSONArray) o).values.equals(this.values); + } + + @Override + public int hashCode() { + // diverge from the original, which doesn't implement hashCode + return this.values.hashCode(); + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONException.java b/src/json-shade/java/org/springframework/graal/json/JSONException.java new file mode 100644 index 000000000..92dc2ad23 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONException.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.json; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * Thrown to indicate a problem with the JSON API. Such problems include: + *
    + *
  • Attempts to parse or construct malformed documents + *
  • Use of null as a name + *
  • Use of numeric types not available to JSON, such as {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + *
  • Lookups using an out of range index or nonexistent name + *
  • Type mismatches on lookups + *
+ *

+ * Although this is a checked exception, it is rarely recoverable. Most callers should + * simply wrap this exception in an unchecked exception and rethrow:

+ *     public JSONArray toJSONObject() {
+ *     try {
+ *         JSONObject result = new JSONObject();
+ *         ...
+ *     } catch (JSONException e) {
+ *         throw new RuntimeException(e);
+ *     }
+ * }
+ */ +public class JSONException extends Exception { + + private static final long serialVersionUID = 1L; + + public JSONException(String s) { + super(s); + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONObject.java b/src/json-shade/java/org/springframework/graal/json/JSONObject.java new file mode 100644 index 000000000..b59d369b0 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONObject.java @@ -0,0 +1,840 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.json; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * A modifiable set of name/value mappings. Names are unique, non-null strings. Values may + * be any mix of {@link JSONObject JSONObjects}, {@link JSONArray JSONArrays}, Strings, + * Booleans, Integers, Longs, Doubles or {@link #NULL}. Values may not be {@code null}, + * {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not + * listed here. + *

+ * This class can coerce values to another type when requested. + *

+ *

+ * This class can look up both mandatory and optional values: + *

    + *
  • Use getType() to retrieve a mandatory value. This fails with a + * {@code JSONException} if the requested name has no value or if the value cannot be + * coerced to the requested type. + *
  • Use optType() to retrieve an optional value. This returns a + * system- or user-supplied default if the requested name has no value or if the value + * cannot be coerced to the requested type. + *
+ *

+ * Warning: this class represents null in two incompatible ways: the + * standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}. + * In particular, calling {@code put(name, null)} removes the named entry from the object + * but {@code put(name, JSONObject.NULL)} stores an entry whose value is + * {@code JSONObject.NULL}. + *

+ * Instances of this class are not thread safe. Although this class is nonfinal, it was + * not designed for inheritance and should not be subclassed. In particular, self-use by + * overrideable methods is not specified. See Effective Java Item 17, "Design and + * Document or inheritance or else prohibit it" for further information. + */ +public class JSONObject { + + private static final Double NEGATIVE_ZERO = -0d; + + /** + * A sentinel value used to explicitly define a name with no value. Unlike + * {@code null}, names with this value: + *

    + *
  • show up in the {@link #names} array + *
  • show up in the {@link #keys} iterator + *
  • return {@code true} for {@link #has(String)} + *
  • do not throw on {@link #get(String)} + *
  • are included in the encoded JSON string. + *
+ *

+ * This value violates the general contract of {@link Object#equals} by returning true + * when compared to {@code null}. Its {@link #toString} method returns "null". + */ + public static final Object NULL = new Object() { + + @Override + public boolean equals(Object o) { + return o == this || o == null; // API specifies this broken equals + // implementation + } + + @Override + public String toString() { + return "null"; + } + + }; + + private final Map nameValuePairs; + + /** + * Creates a {@code JSONObject} with no name/value mappings. + */ + public JSONObject() { + this.nameValuePairs = new LinkedHashMap<>(); + } + + /** + * Creates a new {@code JSONObject} by copying all name/value mappings from the given + * map. + * + * @param copyFrom a map whose keys are of type {@link String} and whose values are of + * supported types. + * @throws NullPointerException if any of the map's keys are null. + */ + /* (accept a raw type for API compatibility) */ + @SuppressWarnings("rawtypes") + public JSONObject(Map copyFrom) { + this(); + Map contentsTyped = copyFrom; + for (Map.Entry entry : contentsTyped.entrySet()) { + /* + * Deviate from the original by checking that keys are non-null and of the + * proper type. (We still defer validating the values). + */ + String key = (String) entry.getKey(); + if (key == null) { + throw new NullPointerException("key == null"); + } + this.nameValuePairs.put(key, wrap(entry.getValue())); + } + } + + /** + * Creates a new {@code JSONObject} with name/value mappings from the next object in + * the tokener. + * @param readFrom a tokener whose nextValue() method will yield a {@code JSONObject}. + * @throws JSONException if the parse fails or doesn't yield a {@code JSONObject}. + */ + public JSONObject(JSONTokener readFrom) throws JSONException { + /* + * Getting the parser to populate this could get tricky. Instead, just parse to + * temporary JSONObject and then steal the data from that. + */ + Object object = readFrom.nextValue(); + if (object instanceof JSONObject) { + this.nameValuePairs = ((JSONObject) object).nameValuePairs; + } + else { + throw JSON.typeMismatch(object, "JSONObject"); + } + } + + /** + * Creates a new {@code JSONObject} with name/value mappings from the JSON string. + * @param json a JSON-encoded string containing an object. + * @throws JSONException if the parse fails or doesn't yield a {@code + * JSONObject}. + */ + public JSONObject(String json) throws JSONException { + this(new JSONTokener(json)); + } + + /** + * Creates a new {@code JSONObject} by copying mappings for the listed names from the + * given object. Names that aren't present in {@code copyFrom} will be skipped. + * @param copyFrom the source + * @param names the property names + * @throws JSONException if an error occurs + */ + public JSONObject(JSONObject copyFrom, String[] names) throws JSONException { + this(); + for (String name : names) { + Object value = copyFrom.opt(name); + if (value != null) { + this.nameValuePairs.put(name, value); + } + } + } + + /** + * Returns the number of name/value mappings in this object. + * @return the number of name/value mappings in this object + */ + public int length() { + return this.nameValuePairs.size(); + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. + * @param name the name of the property + * @param value the value of the property + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, boolean value) throws JSONException { + this.nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. + * @param name the name of the property + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, double value) throws JSONException { + this.nameValuePairs.put(checkName(name), JSON.checkDouble(value)); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. + * @param name the name of the property + * @param value the value of the property + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, int value) throws JSONException { + this.nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. + * @param name the name of the property + * @param value the value of the property + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, long value) throws JSONException { + this.nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. If the value is {@code null}, any existing mapping for {@code name} + * is removed. + * @param name the name of the property + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double, {@link #NULL}, or {@code null}. May not be {@link Double#isNaN() + * NaNs} or {@link Double#isInfinite() infinities}. + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, Object value) throws JSONException { + if (value == null) { + this.nameValuePairs.remove(name); + return this; + } + if (value instanceof Number) { + // deviate from the original by checking all Numbers, not just floats & + // doubles + JSON.checkDouble(((Number) value).doubleValue()); + } + this.nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Equivalent to {@code put(name, value)} when both parameters are non-null; does + * nothing otherwise. + * @param name the name of the property + * @param value the value of the property + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject putOpt(String name, Object value) throws JSONException { + if (name == null || value == null) { + return this; + } + return put(name, value); + } + + /** + * Appends {@code value} to the array already mapped to {@code name}. If this object + * has no mapping for {@code name}, this inserts a new mapping. If the mapping exists + * but its value is not an array, the existing and new values are inserted in order + * into a new array which is itself mapped to {@code name}. In aggregate, this allows + * values to be added to a mapping one at a time. + * @param name the name of the property + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double, {@link #NULL} or null. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject accumulate(String name, Object value) throws JSONException { + Object current = this.nameValuePairs.get(checkName(name)); + if (current == null) { + return put(name, value); + } + + // check in accumulate, since array.put(Object) doesn't do any checking + if (value instanceof Number) { + JSON.checkDouble(((Number) value).doubleValue()); + } + + if (current instanceof JSONArray) { + JSONArray array = (JSONArray) current; + array.put(value); + } + else { + JSONArray array = new JSONArray(); + array.put(current); + array.put(value); + this.nameValuePairs.put(name, array); + } + return this; + } + + String checkName(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + return name; + } + + /** + * Removes the named mapping if it exists; does nothing otherwise. + * + * @param name the name of the property + * @return the value previously mapped by {@code name}, or null if there was no such + * mapping. + */ + public Object remove(String name) { + return this.nameValuePairs.remove(name); + } + + /** + * Returns true if this object has no mapping for {@code name} or if it has a mapping + * whose value is {@link #NULL}. + * @param name the name of the property + * @return true if this object has no mapping for {@code name} + */ + public boolean isNull(String name) { + Object value = this.nameValuePairs.get(name); + return value == null || value == NULL; + } + + /** + * Returns true if this object has a mapping for {@code name}. The mapping may be + * {@link #NULL}. + * @param name the name of the property + * @return true if this object has a mapping for {@code name} + */ + public boolean has(String name) { + return this.nameValuePairs.containsKey(name); + } + + /** + * Returns the value mapped by {@code name}. + * @param name the name of the property + * @return the value + * @throws JSONException if no such mapping exists. + */ + public Object get(String name) throws JSONException { + Object result = this.nameValuePairs.get(name); + if (result == null) { + throw new JSONException("No value for " + name); + } + return result; + } + + /** + * Returns the value mapped by {@code name}, or null if no such mapping exists. + * @param name the name of the property + * @return the value or {@code null} + */ + public Object opt(String name) { + return this.nameValuePairs.get(name); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or can be + * coerced to a boolean. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or cannot be coerced to a + * boolean. + */ + public boolean getBoolean(String name) throws JSONException { + Object object = get(name); + Boolean result = JSON.toBoolean(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "boolean"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or can be + * coerced to a boolean. Returns false otherwise. + * @param name the name of the property + * @return the value or {@code null} + */ + public boolean optBoolean(String name) { + return optBoolean(name, false); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or can be + * coerced to a boolean. Returns {@code fallback} otherwise. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public boolean optBoolean(String name, boolean fallback) { + Object object = opt(name); + Boolean result = JSON.toBoolean(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or can be + * coerced to a double. + * + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or cannot be coerced to a + * double. + */ + public double getDouble(String name) throws JSONException { + Object object = get(name); + Double result = JSON.toDouble(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "double"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or can be + * coerced to a double. Returns {@code NaN} otherwise. + * @param name the name of the property + * @return the value or {@code NaN} + */ + public double optDouble(String name) { + return optDouble(name, Double.NaN); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or can be + * coerced to a double. Returns {@code fallback} otherwise. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public double optDouble(String name, double fallback) { + Object object = opt(name); + Double result = JSON.toDouble(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or can be + * coerced to an int. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or cannot be coerced to an int. + */ + public int getInt(String name) throws JSONException { + Object object = get(name); + Integer result = JSON.toInteger(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "int"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or can be + * coerced to an int. Returns 0 otherwise. + * @param name the name of the property + * @return the value of {@code 0} + */ + public int optInt(String name) { + return optInt(name, 0); + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or can be + * coerced to an int. Returns {@code fallback} otherwise. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public int optInt(String name, int fallback) { + Object object = opt(name); + Integer result = JSON.toInteger(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or can be + * coerced to a long. Note that JSON represents numbers as doubles, so this is + * lossy; use strings to transfer numbers via JSON. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or cannot be coerced to a long. + */ + public long getLong(String name) throws JSONException { + Object object = get(name); + Long result = JSON.toLong(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "long"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or can be + * coerced to a long. Returns 0 otherwise. Note that JSON represents numbers as + * doubles, so this is lossy; use strings to transfer numbers via + * JSON. + * @param name the name of the property + * @return the value or {@code 0L} + */ + public long optLong(String name) { + return optLong(name, 0L); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or can be + * coerced to a long. Returns {@code fallback} otherwise. Note that JSON represents + * numbers as doubles, so this is lossy; use strings to transfer + * numbers via JSON. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public long optLong(String name, long fallback) { + Object object = opt(name); + Long result = JSON.toLong(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if necessary. + * @param name the name of the property + * @return the value + * @throws JSONException if no such mapping exists. + */ + public String getString(String name) throws JSONException { + Object object = get(name); + String result = JSON.toString(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "String"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if necessary. + * Returns the empty string if no such mapping exists. + * @param name the name of the property + * @return the value or an empty string + */ + public String optString(String name) { + return optString(name, ""); + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if necessary. + * Returns {@code fallback} if no such mapping exists. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public String optString(String name, String fallback) { + Object object = opt(name); + String result = JSON.toString(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONArray}. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or is not a {@code + * JSONArray}. + */ + public JSONArray getJSONArray(String name) throws JSONException { + Object object = get(name); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + else { + throw JSON.typeMismatch(name, object, "JSONArray"); + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONArray}. Returns null otherwise. + * @param name the name of the property + * @return the value or {@code null} + */ + public JSONArray optJSONArray(String name) { + Object object = opt(name); + return object instanceof JSONArray ? (JSONArray) object : null; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONObject}. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or is not a {@code + * JSONObject}. + */ + public JSONObject getJSONObject(String name) throws JSONException { + Object object = get(name); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + else { + throw JSON.typeMismatch(name, object, "JSONObject"); + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONObject}. Returns null otherwise. + * @param name the name of the property + * @return the value or {@code null} + */ + public JSONObject optJSONObject(String name) { + Object object = opt(name); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Returns an array with the values corresponding to {@code names}. The array contains + * null for names that aren't mapped. This method returns null if {@code names} is + * either null or empty. + * @param names the names of the properties + * @return the array + */ + public JSONArray toJSONArray(JSONArray names) { + JSONArray result = new JSONArray(); + if (names == null) { + return null; + } + int length = names.length(); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = JSON.toString(names.opt(i)); + result.put(opt(name)); + } + return result; + } + + /** + * Returns an iterator of the {@code String} names in this object. The returned + * iterator supports {@link Iterator#remove() remove}, which will remove the + * corresponding mapping from this object. If this object is modified after the + * iterator is returned, the iterator's behavior is undefined. The order of the keys + * is undefined. + * @return the keys + */ + /* Return a raw type for API compatibility */ + @SuppressWarnings("rawtypes") + public Iterator keys() { + return this.nameValuePairs.keySet().iterator(); + } + + /** + * Returns an array containing the string names in this object. This method returns + * null if this object contains no mappings. + * @return the array + */ + public JSONArray names() { + return this.nameValuePairs.isEmpty() ? null + : new JSONArray(new ArrayList<>(this.nameValuePairs.keySet())); + } + + /** + * Encodes this object as a compact JSON string, such as: + *

{"query":"Pizza","locations":[94043,90210]}
+ * @return a string representation of the object. + */ + @Override + public String toString() { + try { + JSONStringer stringer = new JSONStringer(); + writeTo(stringer); + return stringer.toString(); + } + catch (JSONException e) { + return null; + } + } + + /** + * Encodes this object as a human readable JSON string for debugging, such as:
+	 * {
+	 *     "query": "Pizza",
+	 *     "locations": [
+	 *         94043,
+	 *         90210
+	 *     ]
+	 * }
+ * @param indentSpaces the number of spaces to indent for each level of nesting. + * @return a string representation of the object. + * @throws JSONException if an error occurs + */ + public String toString(int indentSpaces) throws JSONException { + JSONStringer stringer = new JSONStringer(indentSpaces); + writeTo(stringer); + return stringer.toString(); + } + + void writeTo(JSONStringer stringer) throws JSONException { + stringer.object(); + for (Map.Entry entry : this.nameValuePairs.entrySet()) { + stringer.key(entry.getKey()).value(entry.getValue()); + } + stringer.endObject(); + } + + /** + * Encodes the number as a JSON string. + * @param number a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return the encoded value + * @throws JSONException if an error occurs + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Number must be non-null"); + } + + double doubleValue = number.doubleValue(); + JSON.checkDouble(doubleValue); + + // the original returns "-0" instead of "-0.0" for negative zero + if (number.equals(NEGATIVE_ZERO)) { + return "-0"; + } + + long longValue = number.longValue(); + if (doubleValue == longValue) { + return Long.toString(longValue); + } + + return number.toString(); + } + + /** + * Encodes {@code data} as a JSON string. This applies quotes and any necessary + * character escaping. + * @param data the string to encode. Null will be interpreted as an empty string. + * @return the quoted value + */ + public static String quote(String data) { + if (data == null) { + return "\"\""; + } + try { + JSONStringer stringer = new JSONStringer(); + stringer.open(JSONStringer.Scope.NULL, ""); + stringer.value(data); + stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); + return stringer.toString(); + } + catch (JSONException e) { + throw new AssertionError(); + } + } + + /** + * Wraps the given object if necessary. + *

+ * If the object is null or , returns {@link #NULL}. If the object is a + * {@code JSONArray} or {@code JSONObject}, no wrapping is necessary. If the object is + * {@code NULL}, no wrapping is necessary. If the object is an array or + * {@code Collection}, returns an equivalent {@code JSONArray}. If the object is a + * {@code Map}, returns an equivalent {@code JSONObject}. If the object is a primitive + * wrapper type or {@code String}, returns the object. Otherwise if the object is from + * a {@code java} package, returns the result of {@code toString}. If wrapping fails, + * returns null. + * @param o the object to wrap + * @return the wrapped object + */ + @SuppressWarnings("rawtypes") + public static Object wrap(Object o) { + if (o == null) { + return NULL; + } + if (o instanceof JSONArray || o instanceof JSONObject) { + return o; + } + if (o.equals(NULL)) { + return o; + } + try { + if (o instanceof Collection) { + return new JSONArray((Collection) o); + } + else if (o.getClass().isArray()) { + return new JSONArray(o); + } + if (o instanceof Map) { + return new JSONObject((Map) o); + } + if (o instanceof Boolean || o instanceof Byte || o instanceof Character + || o instanceof Double || o instanceof Float || o instanceof Integer + || o instanceof Long || o instanceof Short || o instanceof String) { + return o; + } + if (o.getClass().getPackage().getName().startsWith("java.")) { + return o.toString(); + } + } + catch (Exception ignored) { + } + return null; + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONStringer.java b/src/json-shade/java/org/springframework/graal/json/JSONStringer.java new file mode 100644 index 000000000..3f6d98156 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONStringer.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.json; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most application + * developers should use those methods directly and disregard this API. For example:

+ * JSONObject object = ...
+ * String json = object.toString();
+ *

+ * Stringers only encode well-formed JSON strings. In particular: + *

    + *
  • The stringer must have exactly one top-level array or object. + *
  • Lexical scopes must be balanced: every call to {@link #array} must have a matching + * call to {@link #endArray} and every call to {@link #object} must have a matching call + * to {@link #endObject}. + *
  • Arrays may not contain keys (property names). + *
  • Objects must alternate keys (property names) and values. + *
  • Values are inserted with either literal {@link #value(Object) value} calls, or by + * nesting arrays or objects. + *
+ * Calls that would result in a malformed JSON string will fail with a + * {@link JSONException}. + *

+ * This class provides no facility for pretty-printing (ie. indenting) output. To encode + * indented output, use {@link JSONObject#toString(int)} or + * {@link JSONArray#toString(int)}. + *

+ * Some implementations of the API support at most 20 levels of nesting. Attempts to + * create more than 20 levels of nesting may fail with a {@link JSONException}. + *

+ * Each stringer may be used to encode a single top level value. Instances of this class + * are not thread safe. Although this class is nonfinal, it was not designed for + * inheritance and should not be subclassed. In particular, self-use by overrideable + * methods is not specified. See Effective Java Item 17, "Design and Document or + * inheritance or else prohibit it" for further information. + */ +public class JSONStringer { + + /** + * The output data, containing at most one top-level array or object. + */ + final StringBuilder out = new StringBuilder(); + + /** + * Lexical scoping elements within this stringer, necessary to insert the appropriate + * separator characters (ie. commas and colons) and to detect nesting errors. + */ + enum Scope { + + /** + * An array with no elements requires no separators or newlines before it is + * closed. + */ + EMPTY_ARRAY, + + /** + * An array with at least one value requires a comma and newline before the next + * element. + */ + NONEMPTY_ARRAY, + + /** + * An object with no keys or values requires no separators or newlines before it + * is closed. + */ + EMPTY_OBJECT, + + /** + * An object whose most recent element is a key. The next element must be a value. + */ + DANGLING_KEY, + + /** + * An object with at least one name/value pair requires a comma and newline before + * the next element. + */ + NONEMPTY_OBJECT, + + /** + * A special bracketless array needed by JSONStringer.join() and + * JSONObject.quote() only. Not used for JSON encoding. + */ + NULL + + } + + /** + * Unlike the original implementation, this stack isn't limited to 20 levels of + * nesting. + */ + private final List stack = new ArrayList<>(); + + /** + * A string containing a full set of spaces for a single level of indentation, or null + * for no pretty printing. + */ + private final String indent; + + public JSONStringer() { + this.indent = null; + } + + JSONStringer(int indentSpaces) { + char[] indentChars = new char[indentSpaces]; + Arrays.fill(indentChars, ' '); + this.indent = new String(indentChars); + } + + /** + * Begins encoding a new array. Each call to this method must be paired with a call to + * {@link #endArray}. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer array() throws JSONException { + return open(Scope.EMPTY_ARRAY, "["); + } + + /** + * Ends encoding the current array. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer endArray() throws JSONException { + return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); + } + + /** + * Begins encoding a new object. Each call to this method must be paired with a call + * to {@link #endObject}. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer object() throws JSONException { + return open(Scope.EMPTY_OBJECT, "{"); + } + + /** + * Ends encoding the current object. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer endObject() throws JSONException { + return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); + } + + /** + * Enters a new scope by appending any necessary whitespace and the given bracket. + * @param empty any necessary whitespace + * @param openBracket the open bracket + * @return this object + * @throws JSONException if processing of json failed + */ + JSONStringer open(Scope empty, String openBracket) throws JSONException { + if (this.stack.isEmpty() && this.out.length() > 0) { + throw new JSONException("Nesting problem: multiple top-level roots"); + } + beforeValue(); + this.stack.add(empty); + this.out.append(openBracket); + return this; + } + + /** + * Closes the current scope by appending any necessary whitespace and the given + * bracket. + * @param empty any necessary whitespace + * @param nonempty the current scope + * @param closeBracket the close bracket + * @return the JSON stringer + * @throws JSONException if processing of json failed + */ + JSONStringer close(Scope empty, Scope nonempty, String closeBracket) + throws JSONException { + Scope context = peek(); + if (context != nonempty && context != empty) { + throw new JSONException("Nesting problem"); + } + + this.stack.remove(this.stack.size() - 1); + if (context == nonempty) { + newline(); + } + this.out.append(closeBracket); + return this; + } + + /** + * Returns the value on the top of the stack. + * @return the scope + * @throws JSONException if processing of json failed + */ + private Scope peek() throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + return this.stack.get(this.stack.size() - 1); + } + + /** + * Replace the value on the top of the stack with the given value. + * @param topOfStack the scope at the top of the stack + */ + private void replaceTop(Scope topOfStack) { + this.stack.set(this.stack.size() - 1, topOfStack); + } + + /** + * Encodes {@code value}. + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double or null. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer value(Object value) throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + + if (value instanceof JSONArray) { + ((JSONArray) value).writeTo(this); + return this; + + } + else if (value instanceof JSONObject) { + ((JSONObject) value).writeTo(this); + return this; + } + + beforeValue(); + + if (value == null || value instanceof Boolean || value == JSONObject.NULL) { + this.out.append(value); + + } + else if (value instanceof Number) { + this.out.append(JSONObject.numberToString((Number) value)); + + } + else { + string(value.toString()); + } + + return this; + } + + /** + * Encodes {@code value} to this stringer. + * @param value the value to encode + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer value(boolean value) throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + this.out.append(value); + return this; + } + + /** + * Encodes {@code value} to this stringer. + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer value(double value) throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + this.out.append(JSONObject.numberToString(value)); + return this; + } + + /** + * Encodes {@code value} to this stringer. + * @param value the value to encode + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer value(long value) throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + this.out.append(value); + return this; + } + + private void string(String value) { + this.out.append("\""); + for (int i = 0, length = value.length(); i < length; i++) { + char c = value.charAt(i); + + /* + * From RFC 4627, "All Unicode characters may be placed within the quotation + * marks except for the characters that must be escaped: quotation mark, + * reverse solidus, and the control characters (U+0000 through U+001F)." + */ + switch (c) { + case '"': + case '\\': + case '/': + this.out.append('\\').append(c); + break; + + case '\t': + this.out.append("\\t"); + break; + + case '\b': + this.out.append("\\b"); + break; + + case '\n': + this.out.append("\\n"); + break; + + case '\r': + this.out.append("\\r"); + break; + + case '\f': + this.out.append("\\f"); + break; + + default: + if (c <= 0x1F) { + this.out.append(String.format("\\u%04x", (int) c)); + } + else { + this.out.append(c); + } + break; + } + + } + this.out.append("\""); + } + + private void newline() { + if (this.indent == null) { + return; + } + + this.out.append("\n"); + for (int i = 0; i < this.stack.size(); i++) { + this.out.append(this.indent); + } + } + + /** + * Encodes the key (property name) to this stringer. + * @param name the name of the forthcoming value. May not be null. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer key(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + beforeKey(); + string(name); + return this; + } + + /** + * Inserts any necessary separators and whitespace before a name. Also adjusts the + * stack to expect the key's value. + * @throws JSONException if processing of json failed + */ + private void beforeKey() throws JSONException { + Scope context = peek(); + if (context == Scope.NONEMPTY_OBJECT) { // first in object + this.out.append(','); + } + else if (context != Scope.EMPTY_OBJECT) { // not in an object! + throw new JSONException("Nesting problem"); + } + newline(); + replaceTop(Scope.DANGLING_KEY); + } + + /** + * Inserts any necessary separators and whitespace before a literal value, inline + * array, or inline object. Also adjusts the stack to expect either a closing bracket + * or another element. + * @throws JSONException if processing of json failed + */ + private void beforeValue() throws JSONException { + if (this.stack.isEmpty()) { + return; + } + + Scope context = peek(); + if (context == Scope.EMPTY_ARRAY) { // first in array + replaceTop(Scope.NONEMPTY_ARRAY); + newline(); + } + else if (context == Scope.NONEMPTY_ARRAY) { // another in array + this.out.append(','); + newline(); + } + else if (context == Scope.DANGLING_KEY) { // value for key + this.out.append(this.indent == null ? ":" : ": "); + replaceTop(Scope.NONEMPTY_OBJECT); + } + else if (context != Scope.NULL) { + throw new JSONException("Nesting problem"); + } + } + + /** + * Returns the encoded JSON string. + *

+ * If invoked with unterminated arrays or unclosed objects, this method's return value + * is undefined. + *

+ * Warning: although it contradicts the general contract of + * {@link Object#toString}, this method returns null if the stringer contains no data. + * @return the encoded JSON string. + */ + @Override + public String toString() { + return this.out.length() == 0 ? null : this.out.toString(); + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONTokener.java b/src/json-shade/java/org/springframework/graal/json/JSONTokener.java new file mode 100644 index 000000000..64f20f632 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONTokener.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.json; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * Parses a JSON (RFC 4627) encoded + * string into the corresponding object. Most clients of this class will use only need the + * {@link #JSONTokener(String) constructor} and {@link #nextValue} method. Example usage: + *

+ * String json = "{"
+ *         + "  \"query\": \"Pizza\", "
+ *         + "  \"locations\": [ 94043, 90210 ] "
+ *         + "}";
+ *
+ * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ * String query = object.getString("query");
+ * JSONArray locations = object.getJSONArray("locations");
+ *

+ * For best interoperability and performance use JSON that complies with RFC 4627, such as + * that generated by {@link JSONStringer}. For legacy reasons this parser is lenient, so a + * successful parse does not indicate that the input string was valid JSON. All of the + * following syntax errors will be ignored: + *

    + *
  • End of line comments starting with {@code //} or {@code #} and ending with a + * newline character. + *
  • C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such + * comments may not be nested. + *
  • Strings that are unquoted or {@code 'single quoted'}. + *
  • Hexadecimal integers prefixed with {@code 0x} or {@code 0X}. + *
  • Octal integers prefixed with {@code 0}. + *
  • Array elements separated by {@code ;}. + *
  • Unnecessary array separators. These are interpreted as if null was the omitted + * value. + *
  • Key-value pairs separated by {@code =} or {@code =>}. + *
  • Key-value pairs separated by {@code ;}. + *
+ *

+ * Each tokener may be used to parse a single JSON string. Instances of this class are not + * thread safe. Although this class is nonfinal, it was not designed for inheritance and + * should not be subclassed. In particular, self-use by overrideable methods is not + * specified. See Effective Java Item 17, "Design and Document or inheritance or + * else prohibit it" for further information. + */ +public class JSONTokener { + + /** + * The input JSON. + */ + private final String in; + + /** + * The index of the next character to be returned by {@link #next}. When the input is + * exhausted, this equals the input's length. + */ + private int pos; + + /** + * @param in JSON encoded string. Null is not permitted and will yield a tokener that + * throws {@code NullPointerExceptions} when methods are called. + */ + public JSONTokener(String in) { + // consume an optional byte order mark (BOM) if it exists + if (in != null && in.startsWith("\ufeff")) { + in = in.substring(1); + } + this.in = in; + } + + /** + * Returns the next value from the input. + * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long, + * Double or {@link JSONObject#NULL}. + * @throws JSONException if the input is malformed. + */ + public Object nextValue() throws JSONException { + int c = nextCleanInternal(); + switch (c) { + case -1: + throw syntaxError("End of input"); + + case '{': + return readObject(); + + case '[': + return readArray(); + + case '\'': + case '"': + return nextString((char) c); + + default: + this.pos--; + return readLiteral(); + } + } + + private int nextCleanInternal() throws JSONException { + while (this.pos < this.in.length()) { + int c = this.in.charAt(this.pos++); + switch (c) { + case '\t': + case ' ': + case '\n': + case '\r': + continue; + + case '/': + if (this.pos == this.in.length()) { + return c; + } + + char peek = this.in.charAt(this.pos); + switch (peek) { + case '*': + // skip a /* c-style comment */ + this.pos++; + int commentEnd = this.in.indexOf("*/", this.pos); + if (commentEnd == -1) { + throw syntaxError("Unterminated comment"); + } + this.pos = commentEnd + 2; + continue; + + case '/': + // skip a // end-of-line comment + this.pos++; + skipToEndOfLine(); + continue; + + default: + return c; + } + + case '#': + /* + * Skip a # hash end-of-line comment. The JSON RFC doesn't specify this + * behavior, but it's required to parse existing documents. See + * http://b/2571423. + */ + skipToEndOfLine(); + continue; + + default: + return c; + } + } + + return -1; + } + + /** + * Advances the position until after the next newline character. If the line is + * terminated by "\r\n", the '\n' must be consumed as whitespace by the caller. + */ + private void skipToEndOfLine() { + for (; this.pos < this.in.length(); this.pos++) { + char c = this.in.charAt(this.pos); + if (c == '\r' || c == '\n') { + this.pos++; + break; + } + } + } + + /** + * Returns the string up to but not including {@code quote}, unescaping any character + * escape sequences encountered along the way. The opening quote should have already + * been read. This consumes the closing quote, but does not include it in the returned + * string. + * @param quote either ' or ". + * @return the string up to but not including {@code quote} + * @throws NumberFormatException if any unicode escape sequences are malformed. + * @throws JSONException if processing of json failed + */ + public String nextString(char quote) throws JSONException { + /* + * For strings that are free of escape sequences, we can just extract the result + * as a substring of the input. But if we encounter an escape sequence, we need to + * use a StringBuilder to compose the result. + */ + StringBuilder builder = null; + + /* the index of the first character not yet appended to the builder. */ + int start = this.pos; + + while (this.pos < this.in.length()) { + int c = this.in.charAt(this.pos++); + if (c == quote) { + if (builder == null) { + // a new string avoids leaking memory + return new String(this.in.substring(start, this.pos - 1)); + } + else { + builder.append(this.in, start, this.pos - 1); + return builder.toString(); + } + } + + if (c == '\\') { + if (this.pos == this.in.length()) { + throw syntaxError("Unterminated escape sequence"); + } + if (builder == null) { + builder = new StringBuilder(); + } + builder.append(this.in, start, this.pos - 1); + builder.append(readEscapeCharacter()); + start = this.pos; + } + } + + throw syntaxError("Unterminated string"); + } + + /** + * Unescapes the character identified by the character or characters that immediately + * follow a backslash. The backslash '\' should have already been read. This supports + * both unicode escapes "u000A" and two-character escapes "\n". + * @return the unescaped char + * @throws NumberFormatException if any unicode escape sequences are malformed. + * @throws JSONException if processing of json failed + */ + private char readEscapeCharacter() throws JSONException { + char escaped = this.in.charAt(this.pos++); + switch (escaped) { + case 'u': + if (this.pos + 4 > this.in.length()) { + throw syntaxError("Unterminated escape sequence"); + } + String hex = this.in.substring(this.pos, this.pos + 4); + this.pos += 4; + return (char) Integer.parseInt(hex, 16); + + case 't': + return '\t'; + + case 'b': + return '\b'; + + case 'n': + return '\n'; + + case 'r': + return '\r'; + + case 'f': + return '\f'; + + case '\'': + case '"': + case '\\': + default: + return escaped; + } + } + + /** + * Reads a null, boolean, numeric or unquoted string literal value. Numeric values + * will be returned as an Integer, Long, or Double, in that order of preference. + * @return a literal value + * @throws JSONException if processing of json failed + */ + private Object readLiteral() throws JSONException { + String literal = nextToInternal("{}[]/\\:,=;# \t\f"); + + if (literal.isEmpty()) { + throw syntaxError("Expected literal value"); + } + else if ("null".equalsIgnoreCase(literal)) { + return JSONObject.NULL; + } + else if ("true".equalsIgnoreCase(literal)) { + return Boolean.TRUE; + } + else if ("false".equalsIgnoreCase(literal)) { + return Boolean.FALSE; + } + + /* try to parse as an integral type... */ + if (literal.indexOf('.') == -1) { + int base = 10; + String number = literal; + if (number.startsWith("0x") || number.startsWith("0X")) { + number = number.substring(2); + base = 16; + } + else if (number.startsWith("0") && number.length() > 1) { + number = number.substring(1); + base = 8; + } + try { + long longValue = Long.parseLong(number, base); + if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { + return (int) longValue; + } + else { + return longValue; + } + } + catch (NumberFormatException e) { + /* + * This only happens for integral numbers greater than Long.MAX_VALUE, + * numbers in exponential form (5e-10) and unquoted strings. Fall through + * to try floating point. + */ + } + } + + /* ...next try to parse as a floating point... */ + try { + return Double.valueOf(literal); + } + catch (NumberFormatException ignored) { + } + + /* ... finally give up. We have an unquoted string */ + return new String(literal); // a new string avoids leaking memory + } + + /** + * Returns the string up to but not including any of the given characters or a newline + * character. This does not consume the excluded character. + * @return the string up to but not including any of the given characters or a newline + * character + */ + private String nextToInternal(String excluded) { + int start = this.pos; + for (; this.pos < this.in.length(); this.pos++) { + char c = this.in.charAt(this.pos); + if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { + return this.in.substring(start, this.pos); + } + } + return this.in.substring(start); + } + + /** + * Reads a sequence of key/value pairs and the trailing closing brace '}' of an + * object. The opening brace '{' should have already been read. + * @return an object + * @throws JSONException if processing of json failed + */ + private JSONObject readObject() throws JSONException { + JSONObject result = new JSONObject(); + + /* Peek to see if this is the empty object. */ + int first = nextCleanInternal(); + if (first == '}') { + return result; + } + else if (first != -1) { + this.pos--; + } + + while (true) { + Object name = nextValue(); + if (!(name instanceof String)) { + if (name == null) { + throw syntaxError("Names cannot be null"); + } + else { + throw syntaxError("Names must be strings, but " + name + + " is of type " + name.getClass().getName()); + } + } + + /* + * Expect the name/value separator to be either a colon ':', an equals sign + * '=', or an arrow "=>". The last two are bogus but we include them because + * that's what the original implementation did. + */ + int separator = nextCleanInternal(); + if (separator != ':' && separator != '=') { + throw syntaxError("Expected ':' after " + name); + } + if (this.pos < this.in.length() && this.in.charAt(this.pos) == '>') { + this.pos++; + } + + result.put((String) name, nextValue()); + + switch (nextCleanInternal()) { + case '}': + return result; + case ';': + case ',': + continue; + default: + throw syntaxError("Unterminated object"); + } + } + } + + /** + * Reads a sequence of values and the trailing closing brace ']' of an array. The + * opening brace '[' should have already been read. Note that "[]" yields an empty + * array, but "[,]" returns a two-element array equivalent to "[null,null]". + * @return an array + * @throws JSONException if processing of json failed + */ + private JSONArray readArray() throws JSONException { + JSONArray result = new JSONArray(); + + /* to cover input that ends with ",]". */ + boolean hasTrailingSeparator = false; + + while (true) { + switch (nextCleanInternal()) { + case -1: + throw syntaxError("Unterminated array"); + case ']': + if (hasTrailingSeparator) { + result.put(null); + } + return result; + case ',': + case ';': + /* A separator without a value first means "null". */ + result.put(null); + hasTrailingSeparator = true; + continue; + default: + this.pos--; + } + + result.put(nextValue()); + + switch (nextCleanInternal()) { + case ']': + return result; + case ',': + case ';': + hasTrailingSeparator = true; + continue; + default: + throw syntaxError("Unterminated array"); + } + } + } + + /** + * Returns an exception containing the given message plus the current position and the + * entire input string. + * @param message the message + * @return an exception + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this); + } + + /** + * Returns the current position and the entire input string. + * @return the current position and the entire input string. + */ + @Override + public String toString() { + // consistent with the original implementation + return " at character " + this.pos + " of " + this.in; + } + + /* + * Legacy APIs. + * + * None of the methods below are on the critical path of parsing JSON documents. They + * exist only because they were exposed by the original implementation and may be used + * by some clients. + */ + + public boolean more() { + return this.pos < this.in.length(); + } + + public char next() { + return this.pos < this.in.length() ? this.in.charAt(this.pos++) : '\0'; + } + + public char next(char c) throws JSONException { + char result = next(); + if (result != c) { + throw syntaxError("Expected " + c + " but was " + result); + } + return result; + } + + public char nextClean() throws JSONException { + int nextCleanInt = nextCleanInternal(); + return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; + } + + public String next(int length) throws JSONException { + if (this.pos + length > this.in.length()) { + throw syntaxError(length + " is out of bounds"); + } + String result = this.in.substring(this.pos, this.pos + length); + this.pos += length; + return result; + } + + public String nextTo(String excluded) { + if (excluded == null) { + throw new NullPointerException("excluded == null"); + } + return nextToInternal(excluded).trim(); + } + + public String nextTo(char excluded) { + return nextToInternal(String.valueOf(excluded)).trim(); + } + + public void skipPast(String thru) { + int thruStart = this.in.indexOf(thru, this.pos); + this.pos = thruStart == -1 ? this.in.length() : (thruStart + thru.length()); + } + + public char skipTo(char to) { + int index = this.in.indexOf(to, this.pos); + if (index != -1) { + this.pos = index; + return to; + } + else { + return '\0'; + } + } + + public void back() { + if (--this.pos == -1) { + this.pos = 0; + } + } + + public static int dehexchar(char hex) { + if (hex >= '0' && hex <= '9') { + return hex - '0'; + } + else if (hex >= 'A' && hex <= 'F') { + return hex - 'A' + 10; + } + else if (hex >= 'a' && hex <= 'f') { + return hex - 'a' + 10; + } + else { + return -1; + } + } + +} diff --git a/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java b/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java new file mode 100644 index 000000000..be97bad53 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.util.internal.svm; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "io.netty.util.internal.CleanerJava6",onlyWith= NettyIsAround.class) +final class CleanerJava6Substitution { + private CleanerJava6Substitution() { + } + + @Alias + @RecomputeFieldValue( + kind = RecomputeFieldValue.Kind.FieldOffset, + declClassName = "java.nio.DirectByteBuffer", + name = "cleaner") + private static long CLEANER_FIELD_OFFSET; +} diff --git a/src/main/java/io/netty/util/internal/svm/NettyIsAround.java b/src/main/java/io/netty/util/internal/svm/NettyIsAround.java new file mode 100644 index 000000000..002aec085 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/NettyIsAround.java @@ -0,0 +1,17 @@ +package io.netty.util.internal.svm; + +import java.util.function.BooleanSupplier; + +public class NettyIsAround implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + try { + Class.forName("io.netty.util.internal.CleanerJava6"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + +} diff --git a/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java b/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java new file mode 100644 index 000000000..d4156e2a8 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.util.internal.svm; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "io.netty.util.internal.PlatformDependent0",onlyWith=NettyIsAround.class) +final class PlatformDependent0Substitution { + private PlatformDependent0Substitution() { + } + + @Alias + @RecomputeFieldValue( + kind = RecomputeFieldValue.Kind.FieldOffset, + declClassName = "java.nio.Buffer", + name = "address") + private static long ADDRESS_FIELD_OFFSET; +} diff --git a/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java b/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java new file mode 100644 index 000000000..df49877db --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.util.internal.svm; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "io.netty.util.internal.PlatformDependent",onlyWith=NettyIsAround.class) +final class PlatformDependentSubstitution { + private PlatformDependentSubstitution() { + } + + /** + * The class PlatformDependent caches the byte array base offset by reading the + * field from PlatformDependent0. The automatic recomputation of Substrate VM + * correctly recomputes the field in PlatformDependent0, but since the caching + * in PlatformDependent happens during image building, the non-recomputed value + * is cached. + */ + @Alias + @RecomputeFieldValue( + kind = RecomputeFieldValue.Kind.ArrayBaseOffset, + declClass = byte[].class) + private static long BYTE_ARRAY_BASE_OFFSET; +} diff --git a/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java b/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java new file mode 100644 index 000000000..f14bd3276 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.util.internal.svm; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "io.netty.util.internal.shaded.org.jctools.util.UnsafeRefArrayAccess", onlyWith=NettyIsAround.class) +final class UnsafeRefArrayAccessSubstitution { + private UnsafeRefArrayAccessSubstitution() { + } + + @Alias + @RecomputeFieldValue( + kind = RecomputeFieldValue.Kind.ArrayIndexShift, + declClass = Object[].class) + public static int REF_ELEMENT_SHIFT; +} diff --git a/src/main/java/io/netty/util/internal/svm/package-info.java b/src/main/java/io/netty/util/internal/svm/package-info.java new file mode 100644 index 000000000..e9b82ec03 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * SVM substitutions for classes that will cause trouble while compiling + * into native image. + */ +package io.netty.util.internal.svm; diff --git a/src/main/java/org/springframework/dao/DataAccessException.java b/src/main/java/org/springframework/dao/DataAccessException.java new file mode 100644 index 000000000..187ec4470 --- /dev/null +++ b/src/main/java/org/springframework/dao/DataAccessException.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.dao; + +import org.springframework.core.NestedRuntimeException; +import org.springframework.lang.Nullable; + +/** + * Root of the hierarchy of data access exceptions discussed in + * Expert One-On-One J2EE Design and Development. + * Please see Chapter 9 of this book for detailed discussion of the + * motivation for this package. + * + *

This exception hierarchy aims to let user code find and handle the + * kind of error encountered without knowing the details of the particular + * data access API in use (e.g. JDBC). Thus it is possible to react to an + * optimistic locking failure without knowing that JDBC is being used. + * + *

As this class is a runtime exception, there is no need for user code + * to catch it or subclasses if any error is to be considered fatal + * (the usual case). + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public abstract class DataAccessException extends NestedRuntimeException { + + /** + * Constructor for DataAccessException. + * @param msg the detail message + */ + public DataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for DataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public DataAccessException(@Nullable String msg, @Nullable Throwable cause) { + super(msg, cause); + } + +} diff --git a/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationDescriptor.java b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationDescriptor.java new file mode 100644 index 000000000..8e3f08164 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationDescriptor.java @@ -0,0 +1,117 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.buildtimeinit; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Andy Clement + */ +public class InitializationDescriptor { + + private final List buildtimeClasses; + + private final List buildtimePackages; + + private final List runtimeClasses; + + private final List runtimePackages; + + public InitializationDescriptor() { + this.buildtimeClasses = new ArrayList<>(); + this.buildtimePackages = new ArrayList<>(); + this.runtimeClasses = new ArrayList<>(); + this.runtimePackages = new ArrayList<>(); + } + + public InitializationDescriptor(InitializationDescriptor metadata) { + this.buildtimeClasses = new ArrayList<>(metadata.buildtimeClasses); + this.buildtimePackages = new ArrayList<>(metadata.buildtimePackages); + this.runtimeClasses = new ArrayList<>(metadata.runtimeClasses); + this.runtimePackages = new ArrayList<>(metadata.runtimePackages); + } + + public List getBuildtimeClasses() { + return this.buildtimeClasses; + } + + public List getBuildtimePackages() { + return this.buildtimePackages; + } + + public List getRuntimeClasses() { + return this.runtimeClasses; + } + + public List getRuntimePackages() { + return this.runtimePackages; + } + + public void addBuildtimeClass(String clazz) { + this.buildtimeClasses.add(clazz); + } + + public void addBuildtimePackage(String pkg) { + this.buildtimePackages.add(pkg); + } + + public void addRuntimeClass(String clazz) { + this.runtimeClasses.add(clazz); + } + + public void addRuntimePackage(String pkg) { + this.runtimePackages.add(pkg); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(String.format("InitializationDescriptor #%s buildtime-classes #%s buildtime-packages #%s runtime-classes #%s runtime-packages\n", + buildtimeClasses.size(), buildtimePackages.size(), runtimeClasses.size(), runtimePackages.size())); +// result.append("buildtime classes:\n"); +// this.buildtimeClasses.forEach(cd -> { +// result.append(String.format("%s\n",cd)); +// }); +// result.append("buildtime packages:\n"); +// this.buildtimePackages.forEach(cd -> { +// result.append(String.format("%s\n",cd)); +// }); +// result.append("runtime classes:\n"); +// this.runtimeClasses.forEach(cd -> { +// result.append(String.format("%s\n",cd)); +// }); +// result.append("runtime packages:\n"); +// this.runtimePackages.forEach(cd -> { +// result.append(String.format("%s\n",cd)); +// }); + return result.toString(); + } + + public boolean isEmpty() { + return buildtimeClasses.isEmpty() && runtimeClasses.isEmpty(); + } + + public static InitializationDescriptor of(String jsonString) { + try { + return InitializationJsonMarshaller.read(jsonString); + } catch (Exception e) { + throw new IllegalStateException("Unable to read json:\n"+jsonString, e); + } + } + +} diff --git a/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonConverter.java b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonConverter.java new file mode 100644 index 000000000..14265d0ef --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonConverter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.buildtimeinit; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Converter to change resource descriptor objects into JSON objects + * + * @author Andy Clement + */ +class InitializationJsonConverter { + + public JSONObject toJsonArray(InitializationDescriptor metadata) throws Exception { + JSONObject object = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + for (String p : metadata.getBuildtimeClasses()) { + jsonArray.put(toClassJsonObject(p)); + } + for (String p : metadata.getBuildtimePackages()) { + jsonArray.put(toPackageJsonObject(p)); + } + object.put("buildTimeInitialization", jsonArray); + for (String p : metadata.getRuntimeClasses()) { + jsonArray.put(toClassJsonObject(p)); + } + for (String p : metadata.getRuntimePackages()) { + jsonArray.put(toPackageJsonObject(p)); + } + object.put("runtimeInitialization", jsonArray); + return object; + } + + public JSONObject toPackageJsonObject(String pattern) throws Exception { + JSONObject object = new JSONObject(); + object.put("package", pattern); + return object; + } + + public JSONObject toClassJsonObject(String pattern) throws Exception { + JSONObject object = new JSONObject(); + object.put("class", pattern); + return object; + } +} diff --git a/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonMarshaller.java b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonMarshaller.java new file mode 100644 index 000000000..a319ea7b2 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonMarshaller.java @@ -0,0 +1,131 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.buildtimeinit; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Marshaller to write {@link InitializationDescriptor} as JSON. + * + * @author Andy Clement + */ +public class InitializationJsonMarshaller { + + private static final int BUFFER_SIZE = 4098; + + public void write(InitializationDescriptor metadata, OutputStream outputStream) + throws IOException { + try { + InitializationJsonConverter converter = new InitializationJsonConverter(); + JSONObject jsonObject = converter.toJsonArray(metadata); + outputStream.write(jsonObject.toString(2).getBytes(StandardCharsets.UTF_8)); + } + catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException) ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new IllegalStateException(ex); + } + } + + public static InitializationDescriptor read(String input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + return read(bais); + } + } + + public static InitializationDescriptor read(byte[] input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input)) { + return read(bais); + } + } + + public static InitializationDescriptor read(InputStream inputStream) throws Exception { + InitializationDescriptor metadata = toDelayInitDescriptor(new JSONObject(toString(inputStream))); + return metadata; + } + + private static InitializationDescriptor toDelayInitDescriptor(JSONObject object) throws Exception { + InitializationDescriptor rd = new InitializationDescriptor(); + JSONArray array = object.getJSONArray("buildTimeInitialization"); + for (int i=0;i listOfParameterTypes = null; +// if (parameterTypes != null) { +// listOfParameterTypes = new ArrayList<>(); +// for (int i=0;i proxyDescriptors; + + public ProxiesDescriptor() { + this.proxyDescriptors = new ArrayList<>(); + } + + public ProxiesDescriptor(ProxiesDescriptor metadata) { + this.proxyDescriptors = new ArrayList<>(metadata.proxyDescriptors); + } + + public List getProxyDescriptors() { + return this.proxyDescriptors; + } + + public void add(ProxyDescriptor proxyDescriptor) { + this.proxyDescriptors.add(proxyDescriptor); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(String.format("ProxyDescriptors #%s\n",proxyDescriptors.size())); + this.proxyDescriptors.forEach(cd -> { + result.append(String.format("%s: \n",cd)); + }); + return result.toString(); + } + + public boolean isEmpty() { + return proxyDescriptors.isEmpty(); + } + + public static ProxiesDescriptor of(String jsonString) { + try { + return ProxiesDescriptorJsonMarshaller.read(jsonString); + } catch (Exception e) { + throw new IllegalStateException("Unable to read json:\n"+jsonString, e); + } + } + + public void consume(Consumer> consumer) { + proxyDescriptors.stream().forEach(pd -> consumer.accept(pd.getInterfaces())); + } + +// public boolean hasClassDescriptor(String string) { +// for (ProxyDescriptor cd: classDescriptors) { +// if (cd.getName().equals(string)) { +// return true; +// } +// } +// return false; +// } +// +// public ProxyDescriptor getClassDescriptor(String type) { +// for (ProxyDescriptor cd: classDescriptors) { +// if (cd.getName().equals(type)) { +// return cd; +// } +// } +// return null; +// } + +} diff --git a/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonConverter.java b/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonConverter.java new file mode 100644 index 000000000..f540d40fa --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonConverter.java @@ -0,0 +1,92 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.proxies; + +import java.util.List; + +import org.springframework.graal.json.JSONArray; + +/** + * Converter to change reflection descriptor objects into JSON objects + * + * @author Andy Clement + */ +class ProxiesDescriptorJsonConverter { + + public JSONArray toJsonArray(ProxiesDescriptor metadata) throws Exception { + JSONArray jsonArray = new JSONArray(); + for (ProxyDescriptor cd : metadata.getProxyDescriptors()) { + jsonArray.put(toJsonArray(cd)); + } + return jsonArray; + } + + public JSONArray toJsonArray(ProxyDescriptor pd) throws Exception { + JSONArray jsonArray = new JSONArray(); + List interfaces = pd.getInterfaces(); + for (String intface: interfaces) { + jsonArray.put(intface); + } + return jsonArray; +// JSONObject jsonObject = new JSONObject(); +// jsonObject.put("name", cd.getName()); +// Set flags = cd.getFlags(); +// if (flags != null) { +// for (Flag flag: Flag.values()) { +// if (flags.contains(flag)) { +// putTrueFlag(jsonObject,flag.name()); +// } +// } +// } +// List fds = cd.getFields(); +// if (fds != null) { +// JSONArray fieldJsonArray = new JSONArray(); +// for (FieldDescriptor fd: fds) { +// JSONObject fieldjo = new JSONObject(); +// fieldjo.put("name", fd.getName()); +// if (fd.isAllowWrite()) { +// fieldjo.put("allowWrite", "true"); +// } +// fieldJsonArray.put(fieldjo); +// } +// jsonObject.put("fields", fieldJsonArray); +// } +// List mds = cd.getMethods(); +// if (mds != null) { +// JSONArray methodsJsonArray = new JSONArray(); +// for (MethodDescriptor md: mds) { +// JSONObject methodJsonObject = new JSONObject(); +// methodJsonObject.put("name", md.getName()); +// List parameterTypes = md.getParameterTypes(); +// JSONArray parameterArray = new JSONArray(); +// if (parameterTypes != null) { +// for (String pt: parameterTypes) { +// parameterArray.put(pt); +// } +// } +// methodJsonObject.put("parameterTypes",parameterArray); +// methodsJsonArray.put(methodJsonObject); +// } +// jsonObject.put("methods", methodsJsonArray); +// } +// return jsonObject; + } + +// private void putTrueFlag(JSONObject jsonObject, String name) throws Exception { +// jsonObject.put(name, true); +// } +} diff --git a/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonMarshaller.java b/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonMarshaller.java new file mode 100644 index 000000000..413762dd3 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonMarshaller.java @@ -0,0 +1,141 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.proxies; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.graal.json.JSONArray; + +/** + * Marshaller to write {@link ProxiesDescriptor} as JSON. + * + * @author Andy Clement + */ +public class ProxiesDescriptorJsonMarshaller { + + private static final int BUFFER_SIZE = 4098; + + public void write(ProxiesDescriptor metadata, OutputStream outputStream) + throws IOException { + try { + ProxiesDescriptorJsonConverter converter = new ProxiesDescriptorJsonConverter(); + JSONArray jsonArray = converter.toJsonArray(metadata); + outputStream.write(jsonArray.toString(2).getBytes(StandardCharsets.UTF_8)); + } + catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException) ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new IllegalStateException(ex); + } + } + + public static ProxiesDescriptor read(String input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + return read(bais); + } + } + + public static ProxiesDescriptor read(byte[] input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input)) { + return read(bais); + } + } + + public static ProxiesDescriptor read(InputStream inputStream) throws Exception { + ProxiesDescriptor metadata = toProxiesDescriptor(new JSONArray(toString(inputStream))); + return metadata; + } + + private static ProxiesDescriptor toProxiesDescriptor(JSONArray array) throws Exception { + ProxiesDescriptor pds = new ProxiesDescriptor(); + for (int i=0;i interfaces = new ArrayList<>(); + for (int i=0;i listOfParameterTypes = null; +// if (parameterTypes != null) { +// listOfParameterTypes = new ArrayList<>(); +// for (int i=0;i { + + private List interfaces; // e.g. java.io.Serializable + + ProxyDescriptor() { + } + + ProxyDescriptor(List interfaces) { + this.interfaces = interfaces; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProxyDescriptor other = (ProxyDescriptor) o; + boolean result = true; + result = result && nullSafeEquals(this.interfaces, other.interfaces); + return result; + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.interfaces); + return result; + } + + private boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + return o1.equals(o2); + } + + private int nullSafeHashCode(Object o) { + return (o != null) ? o.hashCode() : 0; + } + + @Override + public String toString() { + StringBuilder string = new StringBuilder(); + buildToStringProperty(string, "interfaces", this.interfaces); + return string.toString(); + } + + protected void buildToStringProperty(StringBuilder string, String property, Object value) { + if (value != null) { + string.append(" ").append(property).append(":").append(value); + } + } + + @Override + public int compareTo(ProxyDescriptor o) { + List l = this.interfaces; + List r = o.interfaces; + if (l.size() != r.size()) { + return l.size() - r.size(); + } + for (int i = 0; i < l.size(); i++) { + int cmpTo = l.get(i).compareTo(r.get(i)); + if (cmpTo != 0) { + return cmpTo; + } + } + return 0; // equal! + } + + public static ProxyDescriptor of(List interfaces) { + ProxyDescriptor pd = new ProxyDescriptor(); + pd.setInterfaces(interfaces); + return pd; + } + + public List getInterfaces() { + return this.interfaces; + } + + public void setInterfaces(List interfaces) { + this.interfaces = new ArrayList<>(); + this.interfaces.addAll(interfaces); + } + + /** + * Used when new data is to be added to an already existing class descriptor + * (additional members, flag settings). + * + * @param cd the ClassDescriptor to merge into this one + */ +// public void merge(ProxyDescriptor cd) { +// for (Flag flag : cd.getFlags()) { +// this.setFlag(flag); +// } +// for (FieldDescriptor fd : cd.getFields()) { +// FieldDescriptor existingSimilarOne = null; +// for (FieldDescriptor existingFd: getFields()) { +// if (existingFd.getName().equals(fd.getName())) { +// existingSimilarOne = existingFd; +// break; +// } +// } +// if (existingSimilarOne != null) { +// if (fd.isAllowWrite()) { +// existingSimilarOne.setAllowWrite(true); +// } +// } else { +// addFieldDescriptor(fd); +// } +// } +// for (MethodDescriptor methodDescriptor : cd.getMethods()) { +// if (!containsMethodDescriptor(methodDescriptor)) { +// addMethodDescriptor(methodDescriptor); +// } +// } +// } + + public boolean containsInterface(String intface) { + return interfaces.contains(intface); + } + +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/ClassDescriptor.java b/src/main/java/org/springframework/graal/domain/reflect/ClassDescriptor.java new file mode 100644 index 000000000..2087d47da --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/ClassDescriptor.java @@ -0,0 +1,232 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.reflect; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/** + * Reflection information about a single class. + * + * @author Andy Clement + * @see ReflectionDescriptor + */ +public final class ClassDescriptor implements Comparable { + + private String name; // e.g. java.lang.Class + + private List fields; + + private List methods; // includes constructors "" + + private Set flags; // Inclusion in list indicates they are set + + public enum Flag { + allPublicFields, // + allDeclaredFields, // + allDeclaredConstructors, // + allPublicConstructors, // + allDeclaredMethods, // + allPublicMethods, // + allDeclaredClasses, // + allPublicClasses; + } + + ClassDescriptor() { + } + + ClassDescriptor(String name, List fields, List methods, Set flags) { + this.name = name; + this.fields = fields; + this.methods = methods; + this.flags = flags; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClassDescriptor other = (ClassDescriptor) o; + boolean result = true; + result = result && nullSafeEquals(this.name, other.name); + result = result && nullSafeEquals(this.flags, other.flags); + result = result && nullSafeEquals(this.fields, other.fields); + result = result && nullSafeEquals(this.methods, other.methods); + return result; + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.name); + result = 31 * result + nullSafeHashCode(this.flags); + result = 31 * result + nullSafeHashCode(this.fields); + result = 31 * result + nullSafeHashCode(this.methods); + return result; + } + + private boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + return o1.equals(o2); + } + + private int nullSafeHashCode(Object o) { + return (o != null) ? o.hashCode() : 0; + } + + @Override + public String toString() { + StringBuilder string = new StringBuilder(this.name); + buildToStringProperty(string, "setFlags", this.flags); + buildToStringProperty(string, "fields", this.fields); + buildToStringProperty(string, "methods", this.methods); + return string.toString(); + } + + protected void buildToStringProperty(StringBuilder string, String property, Object value) { + if (value != null) { + string.append(" ").append(property).append(":").append(value); + } + } + + @Override + public int compareTo(ClassDescriptor o) { + return getName().compareTo(o.getName()); + } + + public Set getFlags() { + return this.flags; + } + + public List getFields() { + return this.fields; + } + + public List getMethods() { + return this.methods; + } + + public static ClassDescriptor of(String name) { + ClassDescriptor cd = new ClassDescriptor(); + cd.setName(name); + return cd; + } + +// public static ClassDescriptor newGroup(String name, String type, String sourceType, +// String sourceMethod) { +// return new ClassDescriptor(ItemType.GROUP, name, null, type, sourceType, +// sourceMethod, null, null, null); +// } +// +// public static ClassDescriptor newProperty(String prefix, String name, String type, +// String sourceType, String sourceMethod, String description, +// Object defaultValue, ItemDeprecation deprecation) { +// return new ClassDescriptor(ItemType.PROPERTY, prefix, name, type, sourceType, +// sourceMethod, description, defaultValue, deprecation); +// } +// +// public static String newItemMetadataPrefix(String prefix, String suffix) { +// return prefix.toLowerCase(Locale.ENGLISH) +// + ReflectionDescriptor.toDashedCase(suffix); +// } + + public void setFlag(Flag f) { + if (flags == null) { + flags = new TreeSet<>(); + } + flags.add(f); + } + + public void addMethodDescriptor(MethodDescriptor methodDescriptor) { + if (methods == null) { + methods = new ArrayList<>(); + } + methods.add(methodDescriptor); + } + + public void addFieldDescriptor(FieldDescriptor fieldDescriptor) { + if (fields == null) { + fields = new ArrayList<>(); + } + fields.add(fieldDescriptor); + } + + /** + * Used when new data is to be added to an already existing class descriptor (additional members, flag settings). + * + * @param cd the ClassDescriptor to merge into this one + */ + public void merge(ClassDescriptor cd) { + for (Flag flag : cd.getFlags()) { + this.setFlag(flag); + } + for (FieldDescriptor fd : cd.getFields()) { + FieldDescriptor existingSimilarOne = null; + for (FieldDescriptor existingFd: getFields()) { + if (existingFd.getName().equals(fd.getName())) { + existingSimilarOne = existingFd; + break; + } + } + if (existingSimilarOne != null) { + if (fd.isAllowWrite()) { + existingSimilarOne.setAllowWrite(true); + } + } else { + addFieldDescriptor(fd); + } + } + for (MethodDescriptor methodDescriptor : cd.getMethods()) { + if (!containsMethodDescriptor(methodDescriptor)) { + addMethodDescriptor(methodDescriptor); + } + } + } + + private boolean containsMethodDescriptor(MethodDescriptor methodDescriptor) { + return methods.contains(methodDescriptor); + } + + public MethodDescriptor getMethodDescriptor(String name,String...parameterTypes) { + MethodDescriptor searchmd = MethodDescriptor.of(name, parameterTypes); + for (MethodDescriptor md: methods) { + if (md.equals(searchmd)) { + return md; + } + } + return null; + } + +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/FieldDescriptor.java b/src/main/java/org/springframework/graal/domain/reflect/FieldDescriptor.java new file mode 100644 index 000000000..5462777b1 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/FieldDescriptor.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.reflect; + +/** + * + * @author Andy Clement + * @see ReflectionDescriptor + */ +public final class FieldDescriptor extends MemberDescriptor implements Comparable { + + private boolean allowWrite = false; + + private boolean allowUnsafeAccess = false; + + FieldDescriptor(String name, boolean allowWrite, boolean allowUnsafeAccess) { + super(name); + this.allowWrite = allowWrite; + this.allowUnsafeAccess = allowUnsafeAccess; + } + + public boolean isAllowWrite() { + return this.allowWrite; + } + + public boolean isAllowUnsafeAccess() { + return this.allowUnsafeAccess; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FieldDescriptor other = (FieldDescriptor) o; + boolean result = true; + result = result && nullSafeEquals(this.name, other.name); + result = result && nullSafeEquals(this.allowWrite, other.allowWrite); + result = result && nullSafeEquals(this.allowUnsafeAccess, other.allowUnsafeAccess); + return result; + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.name); + result = 31 * result + nullSafeHashCode(this.allowWrite); + result = 31 * result + nullSafeHashCode(this.allowUnsafeAccess); + return result; + } + + @Override + public String toString() { + StringBuilder string = new StringBuilder(this.name); + buildToStringProperty(string, "name", this.name); + buildToStringProperty(string, "allowWrite", this.allowWrite); + buildToStringProperty(string, "allowUnsafeAccess", this.allowUnsafeAccess); + return string.toString(); + } + + @Override + public int compareTo(FieldDescriptor o) { + return getName().compareTo(o.getName()); + } + + public void setAllowWrite(boolean b) { + this.allowWrite = b; + } + + public void setAllowUnsafeAccess(boolean b) { + this.allowUnsafeAccess = b; + } + +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/JsonConverter.java b/src/main/java/org/springframework/graal/domain/reflect/JsonConverter.java new file mode 100644 index 000000000..ada7b8602 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/JsonConverter.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.domain.reflect; + +import java.util.List; +import java.util.Set; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; +import org.springframework.graal.domain.reflect.ClassDescriptor.Flag; + +/** + * Converter to change reflection descriptor objects into JSON objects + * + * @author Andy Clement + */ +class JsonConverter { + + public JSONArray toJsonArray(ReflectionDescriptor metadata) throws Exception { + JSONArray jsonArray = new JSONArray(); + for (ClassDescriptor cd : metadata.getClassDescriptors()) { + jsonArray.put(toJsonObject(cd)); + } + return jsonArray; + } + + public JSONObject toJsonObject(ClassDescriptor cd) throws Exception { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", cd.getName()); + Set flags = cd.getFlags(); + if (flags != null) { + for (Flag flag: Flag.values()) { + if (flags.contains(flag)) { + putTrueFlag(jsonObject,flag.name()); + } + } + } + List fds = cd.getFields(); + if (fds != null) { + JSONArray fieldJsonArray = new JSONArray(); + for (FieldDescriptor fd: fds) { + JSONObject fieldjo = new JSONObject(); + fieldjo.put("name", fd.getName()); + if (fd.isAllowWrite()) { + fieldjo.put("allowWrite", "true"); + } + fieldJsonArray.put(fieldjo); + } + jsonObject.put("fields", fieldJsonArray); + } + List mds = cd.getMethods(); + if (mds != null) { + JSONArray methodsJsonArray = new JSONArray(); + for (MethodDescriptor md: mds) { + JSONObject methodJsonObject = new JSONObject(); + methodJsonObject.put("name", md.getName()); + List parameterTypes = md.getParameterTypes(); + JSONArray parameterArray = new JSONArray(); + if (parameterTypes != null) { + for (String pt: parameterTypes) { + parameterArray.put(pt); + } + } + methodJsonObject.put("parameterTypes",parameterArray); + methodsJsonArray.put(methodJsonObject); + } + jsonObject.put("methods", methodsJsonArray); + } + return jsonObject; + } + + private void putTrueFlag(JSONObject jsonObject, String name) throws Exception { + jsonObject.put(name, true); + } +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/JsonMarshaller.java b/src/main/java/org/springframework/graal/domain/reflect/JsonMarshaller.java new file mode 100644 index 000000000..6ba8b7d76 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/JsonMarshaller.java @@ -0,0 +1,142 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.domain.reflect; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.graal.domain.reflect.ClassDescriptor.Flag; +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Marshaller to write {@link ReflectionDescriptor} as JSON. + * + * @author Andy Clement + */ +public class JsonMarshaller { + + private static final int BUFFER_SIZE = 4098; + + public void write(ReflectionDescriptor metadata, OutputStream outputStream) + throws IOException { + try { + JsonConverter converter = new JsonConverter(); + JSONArray jsonArray = converter.toJsonArray(metadata); + outputStream.write(jsonArray.toString(2).getBytes(StandardCharsets.UTF_8)); + } + catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException) ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new IllegalStateException(ex); + } + } + + public static ReflectionDescriptor read(String input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + return read(bais); + } + } + + public static ReflectionDescriptor read(byte[] input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input)) { + return read(bais); + } + } + + public static ReflectionDescriptor read(InputStream inputStream) throws Exception { + ReflectionDescriptor metadata = toReflectionDescriptor(new JSONArray(toString(inputStream))); + return metadata; + } + + private static ReflectionDescriptor toReflectionDescriptor(JSONArray array) throws Exception { + ReflectionDescriptor rd = new ReflectionDescriptor(); + for (int i=0;i listOfParameterTypes = null; + if (parameterTypes != null) { + listOfParameterTypes = new ArrayList<>(); + for (int i=0;i { + + public final static String CONSTRUCTOR_NAME = ""; + + public final static List NO_PARAMS = Collections.emptyList(); + + private List parameterTypes; // e.g. char[], java.lang.String, java.lang.Object[] + + MethodDescriptor() { + } + + MethodDescriptor(String name, List parameterTypes) { + super(name); + this.parameterTypes = parameterTypes; + } + + public List getParameterTypes() { + return this.parameterTypes; + } + + public void setParameterTypes(List parameterTypes) { + this.parameterTypes = parameterTypes; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MethodDescriptor other = (MethodDescriptor) o; + boolean result = true; + result = result && nullSafeEquals(this.name, other.name); + result = result && nullSafeEquals(this.parameterTypes, other.parameterTypes); + return result; + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.name); + result = 31 * result + nullSafeHashCode(this.parameterTypes); + return result; + } + + @Override + public String toString() { + StringBuilder string = new StringBuilder(this.name); + buildToStringProperty(string, "name", this.name); + buildToStringProperty(string, "parameterTypes", this.parameterTypes); + return string.toString(); + } + + @Override + public int compareTo(MethodDescriptor o) { + return getName().compareTo(o.getName()); + } + + public static MethodDescriptor of(String name, String... parameterTypes) { + MethodDescriptor md = new MethodDescriptor(); + md.setName(name); + if (parameterTypes != null) { + md.setParameterTypes(Arrays.asList(parameterTypes)); + } else { + md.setParameterTypes(NO_PARAMS); + } + return md; + } + +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/ReflectionDescriptor.java b/src/main/java/org/springframework/graal/domain/reflect/ReflectionDescriptor.java new file mode 100644 index 000000000..56d89c867 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/ReflectionDescriptor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.reflect; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md + * + * @author Andy Clement + */ +public class ReflectionDescriptor { + + private final List classDescriptors; + + public ReflectionDescriptor() { + this.classDescriptors = new ArrayList<>(); + } + + public ReflectionDescriptor(ReflectionDescriptor metadata) { + this.classDescriptors = new ArrayList<>(metadata.classDescriptors); + } + + public void sort() { + classDescriptors.sort((a,b) -> a.getName().compareTo(b.getName())); + } + + public List getClassDescriptors() { + return this.classDescriptors; + } + + public void add(ClassDescriptor classDescriptor) { + this.classDescriptors.add(classDescriptor); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(String.format("ClassDescriptors #%s\n",classDescriptors.size())); + this.classDescriptors.forEach(cd -> { + result.append(String.format("%s: \n",cd)); + }); + return result.toString(); + } + + public boolean isEmpty() { + return classDescriptors.isEmpty(); + } + + public static ReflectionDescriptor of(String jsonString) { + try { + return JsonMarshaller.read(jsonString); + } catch (Exception e) { + throw new IllegalStateException("Unable to read json:\n"+jsonString, e); + } + } + + public boolean hasClassDescriptor(String string) { + for (ClassDescriptor cd: classDescriptors) { + if (cd.getName().equals(string)) { + return true; + } + } + return false; + } + + public ClassDescriptor getClassDescriptor(String type) { + for (ClassDescriptor cd: classDescriptors) { + if (cd.getName().equals(type)) { + return cd; + } + } + return null; + } + +} diff --git a/src/main/java/org/springframework/graal/domain/resources/ResourcesDescriptor.java b/src/main/java/org/springframework/graal/domain/resources/ResourcesDescriptor.java new file mode 100644 index 000000000..0f8bcc95c --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/resources/ResourcesDescriptor.java @@ -0,0 +1,86 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.domain.resources; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://github.com/oracle/graal/blob/master/substratevm/RESOURCES.md + * + * @author Andy Clement + */ +public class ResourcesDescriptor { + + private final List patterns; + + public ResourcesDescriptor() { + this.patterns = new ArrayList<>(); + } + + public ResourcesDescriptor(ResourcesDescriptor metadata) { + this.patterns = new ArrayList<>(metadata.patterns); + } + + public List getPatterns() { + return this.patterns; + } + + public void add(String pattern) { + this.patterns.add(pattern); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(String.format("ResourcesDescriptors #%s\n",patterns.size())); + this.patterns.forEach(cd -> { + result.append(String.format("%s: \n",cd)); + }); + return result.toString(); + } + + public boolean isEmpty() { + return patterns.isEmpty(); + } + + public static ResourcesDescriptor of(String jsonString) { + try { + return ResourcesJsonMarshaller.read(jsonString); + } catch (Exception e) { + throw new IllegalStateException("Unable to read json:\n"+jsonString, e); + } + } + +// public boolean hasClassDescriptor(String string) { +// for (ProxyDescriptor cd: classDescriptors) { +// if (cd.getName().equals(string)) { +// return true; +// } +// } +// return false; +// } +// +// public ProxyDescriptor getClassDescriptor(String type) { +// for (ProxyDescriptor cd: classDescriptors) { +// if (cd.getName().equals(type)) { +// return cd; +// } +// } +// return null; +// } + +} diff --git a/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonConverter.java b/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonConverter.java new file mode 100644 index 000000000..11e152e44 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonConverter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.resources; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Converter to change resource descriptor objects into JSON objects + * + * @author Andy Clement + */ +class ResourcesJsonConverter { + + public JSONObject toJsonArray(ResourcesDescriptor metadata) throws Exception { + JSONObject object = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + for (String p : metadata.getPatterns()) { + jsonArray.put(toJsonObject(p)); + } + object.put("resources", jsonArray); + return object; + } + + public JSONObject toJsonObject(String pattern) throws Exception { + JSONObject object = new JSONObject(); + object.put("pattern", pattern); + return object; +// JSONObject jsonObject = new JSONObject(); +// jsonObject.put("name", cd.getName()); +// Set flags = cd.getFlags(); +// if (flags != null) { +// for (Flag flag: Flag.values()) { +// if (flags.contains(flag)) { +// putTrueFlag(jsonObject,flag.name()); +// } +// } +// } +// List fds = cd.getFields(); +// if (fds != null) { +// JSONArray fieldJsonArray = new JSONArray(); +// for (FieldDescriptor fd: fds) { +// JSONObject fieldjo = new JSONObject(); +// fieldjo.put("name", fd.getName()); +// if (fd.isAllowWrite()) { +// fieldjo.put("allowWrite", "true"); +// } +// fieldJsonArray.put(fieldjo); +// } +// jsonObject.put("fields", fieldJsonArray); +// } +// List mds = cd.getMethods(); +// if (mds != null) { +// JSONArray methodsJsonArray = new JSONArray(); +// for (MethodDescriptor md: mds) { +// JSONObject methodJsonObject = new JSONObject(); +// methodJsonObject.put("name", md.getName()); +// List parameterTypes = md.getParameterTypes(); +// JSONArray parameterArray = new JSONArray(); +// if (parameterTypes != null) { +// for (String pt: parameterTypes) { +// parameterArray.put(pt); +// } +// } +// methodJsonObject.put("parameterTypes",parameterArray); +// methodsJsonArray.put(methodJsonObject); +// } +// jsonObject.put("methods", methodsJsonArray); +// } +// return jsonObject; + } + +// private void putTrueFlag(JSONObject jsonObject, String name) throws Exception { +// jsonObject.put(name, true); +// } +} diff --git a/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonMarshaller.java b/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonMarshaller.java new file mode 100644 index 000000000..b0f3e4117 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonMarshaller.java @@ -0,0 +1,113 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.domain.resources; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Marshaller to write {@link ResourcesDescriptor} as JSON. + * + * @author Andy Clement + */ +public class ResourcesJsonMarshaller { + + private static final int BUFFER_SIZE = 4098; + + public void write(ResourcesDescriptor metadata, OutputStream outputStream) + throws IOException { + try { + ResourcesJsonConverter converter = new ResourcesJsonConverter(); + JSONObject jsonObject = converter.toJsonArray(metadata); + outputStream.write(jsonObject.toString(2).getBytes(StandardCharsets.UTF_8)); + } + catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException) ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new IllegalStateException(ex); + } + } + + public static ResourcesDescriptor read(String input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + return read(bais); + } + } + + public static ResourcesDescriptor read(byte[] input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input)) { + return read(bais); + } + } + + public static ResourcesDescriptor read(InputStream inputStream) throws Exception { + ResourcesDescriptor metadata = toResourcesDescriptor(new JSONObject(toString(inputStream))); + return metadata; + } + + private static ResourcesDescriptor toResourcesDescriptor(JSONObject object) throws Exception { + ResourcesDescriptor rd = new ResourcesDescriptor(); + JSONArray array = object.getJSONArray("resources"); + for (int i=0;i listOfParameterTypes = null; +// if (parameterTypes != null) { +// listOfParameterTypes = new ArrayList<>(); +// for (int i=0;i> proxyRegisteringConsumer = interfaceNames -> { + System.out.println("- "+interfaceNames); + boolean isOK= true; + Class[] interfaces = new Class[interfaceNames.size()]; + for (int i = 0; i < interfaceNames.size(); i++) { + String className = interfaceNames.get(i); + Class clazz = imageClassLoader.findClassByName(className, false); + if (clazz == null) { + System.out.println("Skipping dynamic proxy registration due to missing type: "+className); + isOK=false; + break; + } + if (!clazz.isInterface()) { + throw new RuntimeException("The class \"" + className + "\" is not an interface."); + } + interfaces[i] = clazz; + } + if (isOK) { + /* The interfaces array can be empty. The java.lang.reflect.Proxy API allows it. */ + dynamicProxySupport.addProxyClass(interfaces); + } + }; + pd.consume(proxyRegisteringConsumer); + } +} diff --git a/src/main/java/org/springframework/graal/support/InitializationHandler.java b/src/main/java/org/springframework/graal/support/InitializationHandler.java new file mode 100644 index 000000000..a8935b5fa --- /dev/null +++ b/src/main/java/org/springframework/graal/support/InitializationHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.support; + +import java.io.InputStream; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; +import org.springframework.graal.domain.buildtimeinit.InitializationDescriptor; +import org.springframework.graal.domain.buildtimeinit.InitializationJsonMarshaller; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; + +/** + * + * @author Andy Clement + */ +public class InitializationHandler { + + public InitializationDescriptor compute() { + try { + InputStream s = this.getClass().getResourceAsStream("/initialization.json"); + return InitializationJsonMarshaller.read(s); + } catch (Exception e) { + return null; + } + } + + public void register(BeforeAnalysisAccess access) { + InitializationDescriptor id = compute(); + System.out.println("SBG: forcing explicit class initialization at build or runtime:"); + System.out.println(id.toString()); + List collect = id.getBuildtimeClasses().stream() + .map(access::findClassByName).filter(Objects::nonNull).collect(Collectors.toList()); + RuntimeClassInitialization.initializeAtBuildTime(collect.toArray(new Class[] {})); + id.getRuntimeClasses().stream() + .map(access::findClassByName).filter(Objects::nonNull) + .forEach(RuntimeClassInitialization::initializeAtRunTime); + System.out.println("Registering these packages for buildtime initialization: \n"+id.getBuildtimePackages()); + RuntimeClassInitialization.initializeAtBuildTime(id.getBuildtimePackages().toArray(new String[] {})); + System.out.println("Registering these packages for runtime initialization: \n"+id.getRuntimePackages()); + RuntimeClassInitialization.initializeAtRunTime(id.getRuntimePackages().toArray(new String[] {})); + } + +} diff --git a/src/main/java/org/springframework/graal/support/ReflectionHandler.java b/src/main/java/org/springframework/graal/support/ReflectionHandler.java new file mode 100644 index 000000000..eab07fa15 --- /dev/null +++ b/src/main/java/org/springframework/graal/support/ReflectionHandler.java @@ -0,0 +1,341 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.support; + +import java.io.InputStream; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Array; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature.DuringSetupAccess; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; +import org.graalvm.util.GuardedAnnotationAccess; +import org.springframework.graal.domain.reflect.ClassDescriptor; +import org.springframework.graal.domain.reflect.ClassDescriptor.Flag; +import org.springframework.graal.domain.reflect.FieldDescriptor; +import org.springframework.graal.domain.reflect.JsonMarshaller; +import org.springframework.graal.domain.reflect.MethodDescriptor; +import org.springframework.graal.domain.reflect.ReflectionDescriptor; + +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.config.ReflectionRegistryAdapter; + +/** + * Loads up the constant data defined in resource file and registers reflective access being + * necessary with the image build. Also provides an method (addAccess(String typename, Flag... flags)} + * usable from elsewhere when needing to register reflective access to a type (e.g. used when resource + * processing). + * + * @author Andy Clement + */ +public class ReflectionHandler { + + private final static String RESOURCE_FILE = "/reflect.json"; + + private ReflectionRegistryAdapter rra; + + private ReflectionDescriptor constantReflectionDescriptor; + + private ImageClassLoader cl; + + public ReflectionDescriptor getConstantData() { + if (constantReflectionDescriptor == null) { + try { + InputStream s = this.getClass().getResourceAsStream(RESOURCE_FILE); + constantReflectionDescriptor = JsonMarshaller.read(s); + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly can't load "+RESOURCE_FILE, e); + } + } + return constantReflectionDescriptor; + } + + public void register(DuringSetupAccess a) { + DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; + RuntimeReflectionSupport rrs = ImageSingletons.lookup(RuntimeReflectionSupport.class); + cl = access.getImageClassLoader(); + rra = new ReflectionRegistryAdapter(rrs, cl); + ReflectionDescriptor reflectionDescriptor = getConstantData(); + + System.out.println("SBG: reflection registering #"+reflectionDescriptor.getClassDescriptors().size()+" entries"); + for (ClassDescriptor classDescriptor : reflectionDescriptor.getClassDescriptors()) { + Class type = null; + String n2 = classDescriptor.getName(); + if (n2.endsWith("[]")) { + System.out.println("ARRAY: "+n2.substring(0,n2.length()-2)); + type = rra.resolveType(n2.substring(0,n2.length()-2)); + System.out.println("Array base type resolved as "+type.getName()); + Object o = Array.newInstance(type, 1); + type = o.getClass(); + System.out.println("Class of array is "+type.getName()); + } else { + type = rra.resolveType(classDescriptor.getName()); + } + if (type == null) { + System.out.println("SBG: WARNING: "+RESOURCE_FILE+" included "+classDescriptor.getName()+" but it doesn't exist on the classpath, skipping..."); + continue; + } + rra.registerType(type); + Set flags = classDescriptor.getFlags(); + if (flags != null) { + for (Flag flag: flags) { + try { + switch (flag) { + case allDeclaredClasses: + rra.registerDeclaredClasses(type); + break; + case allDeclaredFields: + rra.registerDeclaredFields(type); + break; + case allPublicFields: + rra.registerPublicFields(type); + break; + case allDeclaredConstructors: + rra.registerDeclaredConstructors(type); + break; + case allPublicConstructors: + rra.registerPublicConstructors(type); + break; + case allDeclaredMethods: + rra.registerDeclaredMethods(type); + break; + case allPublicMethods: + rra.registerPublicMethods(type); + break; + case allPublicClasses: + rra.registerPublicClasses(type); + break; + } + } catch (NoClassDefFoundError ncdfe) { + System.out.println("SBG: ERROR: problem handling flag: "+flag+" for "+type.getName()+" because of missing "+ncdfe.getMessage()); + } + } + } + + // Process all specific methods defined in the input class descriptor (including constructors) + List methods = classDescriptor.getMethods(); + if (methods != null) { + for (MethodDescriptor methodDescriptor : methods) { + String n = methodDescriptor.getName(); + List parameterTypes = methodDescriptor.getParameterTypes(); + if (parameterTypes == null) { + if (n.equals("")) { + rra.registerAllConstructors(type); + } else { + rra.registerAllMethodsWithName(type, n); + } + } else { + List> collect = parameterTypes.stream().map(pname -> rra.resolveType(pname)) + .collect(Collectors.toList()); + try { + if (n.equals("")) { + rra.registerConstructor(type, collect); + } else { + rra.registerMethod(type, n, collect); + } + } catch (NoSuchMethodException nsme) { + throw new IllegalStateException("Couldn't find: " + methodDescriptor.toString(), nsme); + } + } + } + } + + // Process all specific fields defined in the input class descriptor + List fields = classDescriptor.getFields(); + if (fields != null) { + for (FieldDescriptor fieldDescriptor : fields) { + try { + rra.registerField(type, fieldDescriptor.getName(), fieldDescriptor.isAllowWrite(),fieldDescriptor.isAllowUnsafeAccess()); + } catch (NoSuchFieldException nsfe) { + throw new IllegalStateException("Couldn't find field: " + type.getName()+"."+fieldDescriptor.getName(), nsfe); +// System.out.println("SBG: WARNING: skipping reflection registration of field "+type.getName()+"."+fieldDescriptor.getName()+": field not found"); + } + } + } + } + registerLogback(); + } + + // TODO review - not strictly correct as they may ask with different flags (but right now they don't) + public static final Set added = new HashSet<>(); + + /** + * Record that reflective access to a type (and a selection of its members based on the flags) should + * be possible at runtime. This method will pre-emptively check all type references to ensure later + * native-image processing will not fail if, for example, it trips up over a type reference in a + * generic type that isn't on the image building classpath. NOTE: it is assumed that if elements are + * not accessible that the runtime doesn't need them (this is done under the spring model where + * conditional checks on auto configuration would cause no attempts to be made to types/members that + * aren't added here). + * + * @param typename the dotted type name for which to add reflective access + * @param flags any members that should be accessible via reflection + * @return the class, if the type was successfully registered for reflective access, otherwise null + */ + public Class addAccess(String typename, Flag...flags) { + if (!added.add(typename)) { + return null; + } + System.out.println("SBG: INFO: Registering reflective access to "+typename); + // This can return null if, for example, the supertype of the specified type is not + // on the classpath. In a simple app there may be a number of types coming in from + // spring-boot-autoconfigure but they extend types not on the classpath. + Class type = rra.resolveType(typename); + if (type == null) { + System.out.println("SBG: ERROR: CANNOT RESOLVE "+typename+" ???"); + return null; + } + if (constantReflectionDescriptor.hasClassDescriptor(typename)) { + System.out.println("SBG: WARNING: type "+typename+" being added dynamically whilst "+RESOURCE_FILE+ + " already contains it - does it need to be in the file? "); + } + rra.registerType(type); + for (Flag flag: flags) { + try { + switch (flag) { + case allDeclaredClasses: + if (verify(type.getDeclaredClasses())) { + rra.registerDeclaredClasses(type); + } + break; + case allDeclaredFields: + if (verify(type.getDeclaredFields())) { + rra.registerDeclaredFields(type); + } + break; + case allPublicFields: + if (verify(type.getFields())) { + rra.registerPublicFields(type); + } + break; + case allDeclaredConstructors: + if (verify(type.getDeclaredConstructors())) { + rra.registerDeclaredConstructors(type); + } + break; + case allPublicConstructors: + if (verify(type.getConstructors())) { + rra.registerPublicConstructors(type); + } + break; + case allDeclaredMethods: + if (verify(type.getDeclaredMethods())) { + rra.registerDeclaredMethods(type); + } + break; + case allPublicMethods: + if (verify(type.getMethods())) { + rra.registerPublicMethods(type); + } + break; + case allPublicClasses: + if (verify(type.getClasses())) { + rra.registerPublicClasses(type); + } + break; + } + } catch (NoClassDefFoundError ncdfe) { + System.out.println("SBG: ERROR: problem handling flag: "+flag+" for "+type.getName()+" because of missing "+ncdfe.getMessage()); + } + } + return type; + } + + + private boolean verify(Object[] things) { + for (Object o: things) { + try { + if (o instanceof Method) { + ((Method)o).getGenericReturnType(); + } + if (o instanceof Field) { + ((Field)o).getGenericType(); + } + if (o instanceof AccessibleObject) { + AccessibleObject accessibleObject = (AccessibleObject) o; + GuardedAnnotationAccess.getDeclaredAnnotations(accessibleObject); + } + + if (o instanceof Parameter) { + Parameter parameter = (Parameter) o; + parameter.getType(); + } + if (o instanceof Executable) { + Executable e = (Executable)o; + e.getGenericParameterTypes(); + e.getGenericExceptionTypes(); + e.getParameters(); + } + } catch (Exception e) { + System.out.println("REFLECTION PROBLEM LATER due to reference from "+o+" to "+e.getMessage()); + return false; + } + } + return true; + } + + + // TODO this is horrible, it should be packaged with logback + // from PatternLayout + private String logBackPatterns[] = new String[] { "ch.qos.logback.core.pattern.IdentityCompositeConverter", "ch.qos.logback.core.pattern.ReplacingCompositeConverter", + "DateConverter", "RelativeTimeConverter", "LevelConverter", "ThreadConverter", "LoggerConverter", + "MessageConverter", "ClassOfCallerConverter", "MethodOfCallerConverter", "LineOfCallerConverter", + "FileOfCallerConverter", "MDCConverter", "ThrowableProxyConverter", "RootCauseFirstThrowableProxyConverter", + "ExtendedThrowableProxyConverter", "NopThrowableInformationConverter", "ContextNameConverter", + "CallerDataConverter", "MarkerConverter", "PropertyConverter", "LineSeparatorConverter", + "color.BlackCompositeConverter", "color.RedCompositeConverter", "color.GreenCompositeConverter", + "color.YellowCompositeConverter", "color.BlueCompositeConverter", "color.MagentaCompositeConverter", + "color.CyanCompositeConverter", "color.WhiteCompositeConverter", "color.GrayCompositeConverter", + "color.BoldRedCompositeConverter", "color.BoldGreenCompositeConverter", + "color.BoldYellowCompositeConverter", "color.BoldBlueCompositeConverter", + "color.BoldMagentaCompositeConverter", "color.BoldCyanCompositeConverter", + "color.BoldWhiteCompositeConverter", "ch.qos.logback.classic.pattern.color.HighlightingCompositeConverter", + "LocalSequenceNumberConverter", "org.springframework.boot.logging.logback.ColorConverter", + "org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter", + "org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"}; +// what would a reflection hint look like here? Would it specify maven coords for logback as a requirement on the classpath? +// does logback have a feature? or meta data files for graal? + private void registerLogback() { + try { + addAccess("ch.qos.logback.core.Appender", Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + } catch (NoClassDefFoundError e) { + System.out.println("Logback not found, skipping registration logback types"); + return; + } + addAccess("org.springframework.boot.logging.logback.LogbackLoggingSystem", Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + for (String p: logBackPatterns) { + if (p.startsWith("org")) { + addAccess(p, Flag.allDeclaredConstructors,Flag.allDeclaredMethods); + } else if (p.startsWith("ch.")) { + addAccess(p, Flag.allDeclaredConstructors,Flag.allDeclaredMethods); + } else if (p.startsWith("color.")) { + addAccess("ch.qos.logback.core.pattern."+p,Flag.allDeclaredConstructors,Flag.allDeclaredMethods); + } else { + addAccess("ch.qos.logback.classic.pattern."+p,Flag.allDeclaredConstructors,Flag.allDeclaredMethods); + } + } + } + +} diff --git a/src/main/java/org/springframework/graal/support/ResourcesHandler.java b/src/main/java/org/springframework/graal/support/ResourcesHandler.java new file mode 100644 index 000000000..397fedbe8 --- /dev/null +++ b/src/main/java/org/springframework/graal/support/ResourcesHandler.java @@ -0,0 +1,610 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.support; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; +import org.springframework.graal.domain.reflect.ClassDescriptor.Flag; +import org.springframework.graal.domain.resources.ResourcesDescriptor; +import org.springframework.graal.domain.resources.ResourcesJsonMarshaller; +import org.springframework.graal.type.HintDescriptor; +import org.springframework.graal.type.MissingTypeException; +import org.springframework.graal.type.Type; +import org.springframework.graal.type.TypeSystem; + +import com.oracle.svm.core.jdk.Resources; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.core.configure.ResourcesRegistry; + +public class ResourcesHandler { + + private TypeSystem ts; + + private ImageClassLoader cl; + + private ReflectionHandler reflectionHandler; + + private static boolean REMOVE_UNNECESSARY_CONFIGURATIONS; + + static { + REMOVE_UNNECESSARY_CONFIGURATIONS = Boolean.valueOf(System.getProperty("removeUnusedAutoconfig","false")); + System.out.println("Remove unused config = "+REMOVE_UNNECESSARY_CONFIGURATIONS); + } + + public ResourcesHandler(ReflectionHandler reflectionHandler) { + this.reflectionHandler = reflectionHandler; + } + + public ResourcesDescriptor compute() { + try { + InputStream s = this.getClass().getResourceAsStream("/resources.json"); + ResourcesDescriptor read = ResourcesJsonMarshaller.read(s); + return read; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public void register(BeforeAnalysisAccess access) { + cl = ((BeforeAnalysisAccessImpl) access).getImageClassLoader(); + ts = TypeSystem.get(cl.getClasspath()); + ResourcesDescriptor rd = compute(); + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + // Patterns can be added to the registry, resources can be directly registered + // against Resources + // resourcesRegistry.addResources("*"); + // Resources.registerResource(relativePath, inputstream); + System.out.println("SBG: adding resources - #" + rd.getPatterns().size()+" patterns"); + + for (String pattern : rd.getPatterns()) { + if (pattern.equals("META-INF/spring.factories")) { + continue; // leave to special handling... + } +// if (pattern.contains("logging.properties")) { +// URL resource = cl.getClassLoader().getResource(pattern); +// System.out.println("Can I find "+pattern+"? "+resource); +// } + resourcesRegistry.addResources(pattern); + } + processSpringFactories(); + processSpringComponents(); + } + + public void processSpringComponents() { + Enumeration springComponents = fetchResources("META-INF/spring.components"); + if (springComponents.hasMoreElements()) { + log("Processing META-INF/spring.components files..."); + while (springComponents.hasMoreElements()) { + URL springFactory = springComponents.nextElement(); + processSpringComponents(ts, springFactory); + } + } else { +// System.out.println("No META-INF/spring.components found"); + System.out.println("Found no META-INF/spring.components -> generating one..."); + List> components = scanClasspathForIndexedStereotypes(); + List> filteredComponents = filterComponents(components); + Properties p = new Properties(); + for (Entry filteredComponent: filteredComponents) { + String k = filteredComponent.getKey(); + System.out.println("- "+k); + p.put(k, filteredComponent.getValue()); + reflectionHandler.addAccess(k,Flag.allDeclaredConstructors, Flag.allDeclaredMethods, Flag.allDeclaredClasses); + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + resourcesRegistry.addResources(k.replace(".", "/")+".class"); + processComponent(k, new HashSet<>()); + } + System.out.println("Computed spring.components is "); + System.out.println("vvv"); + p.list(System.out); + System.out.println("^^^"); + System.out.println(">>> "+p.toString()); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + p.store(baos,""); + baos.close(); + byte[] bs = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bs); + Resources.registerResource("META-INF/spring.components", bais); + System.out.println("BAOS: "+new String(bs)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } + + private void processComponent(String typename, Set visited) { + if (!visited.add(typename)) { + return; + } + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + Type componentType = ts.resolveDotted(typename); + System.out.println("> Component processing: "+typename); + List conditionalTypes = componentType.findConditionalOnClassValue(); + if (conditionalTypes != null) { + for (String lDescriptor : conditionalTypes) { + Type t = ts.Lresolve(lDescriptor, true); + boolean exists = (t != null); + if (!exists) { + return; + } else { + try { + reflectionHandler.addAccess(lDescriptor.substring(1,lDescriptor.length()-1).replace("/", "."),Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(lDescriptor.substring(1,lDescriptor.length()-1)+".class"); + } catch (NoClassDefFoundError e) { + System.out.println("Conditional type "+fromLtoDotted(lDescriptor)+" not found for component "+componentType.getName()); + } + + } + } + } + try { + // String configNameDotted = configType.getName().replace("/","."); + System.out.println("Including auto-configuration "+typename); + reflectionHandler.addAccess(typename,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(typename.replace(".", "/")+".class"); + } catch (NoClassDefFoundError e) { + // Example: + // PROBLEM? Can't register Type:org/springframework/boot/autoconfigure/web/servlet/HttpEncodingAutoConfiguration because cannot find javax/servlet/Filter + // java.lang.NoClassDefFoundError: javax/servlet/Filter + // ... at com.oracle.svm.hosted.config.ReflectionRegistryAdapter.registerDeclaredConstructors(ReflectionRegistryAdapter.java:97) + System.out.println("PROBLEM? Can't register "+typename+" because cannot find "+e.getMessage()); + } + + Map> imports = componentType.findImports(); + if (imports != null) { + System.out.println("Imports found on "+typename+" are "+imports); + for (Map.Entry> importsEntry: imports.entrySet()) { + reflectionHandler.addAccess(importsEntry.getKey(),Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + for (String imported: importsEntry.getValue()) { + String importedName = fromLtoDotted(imported); + try { + Type t = ts.resolveDotted(importedName); + processComponent( t.getName().replace("/", "."), visited); + } catch (MissingTypeException mte) { + System.out.println("Cannot find imported "+importedName+" so skipping processing that"); + } + } + } + } + + // Without this code, error at: + // java.lang.ClassNotFoundException cannot be cast to java.lang.Class[] + // at org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector$ConfigurationPropertiesBeanRegistrar.lambda$collectClasses$1(EnableConfigurationPropertiesImportSelector.java:80) + List ecProperties = componentType.findEnableConfigurationPropertiesValue(); + if (ecProperties != null) { + for (String ecPropertyDescriptor: ecProperties) { + String ecPropertyName = fromLtoDotted(ecPropertyDescriptor); + System.out.println("ECP "+ecPropertyName); + try { + reflectionHandler.addAccess(ecPropertyName,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(ecPropertyName.replace(".", "/")+".class"); + } catch (NoClassDefFoundError e) { + System.out.println("Not found for registration: "+ecPropertyName); + } + } + } + + // Find @Bean methods and add them +// List methodsWithAtBean = configType.getMethodsWithAtBean(); +// if (methodsWithAtBean.size() != 0) { +// System.out.println(configType+" here they are: "+ +// methodsWithAtBean.stream().map(m -> m.getName()+m.getDesc()).collect(Collectors.toList())); +// for (Method m: methodsWithAtBean) { +// String desc = m.getDesc(); +// String retType = desc.substring(desc.lastIndexOf(")")+1); //Lorg/springframework/boot/task/TaskExecutorBuilder; +// System.out.println("@Bean return type "+retType); +// reflectionHandler.addAccess(fromLtoDotted(retType), Flag.allDeclaredConstructors, Flag.allDeclaredMethods); +// } +// } + + List nestedTypes = componentType.getNestedTypes(); + for (Type t: nestedTypes) { + if (visited.add(t.getName())) { + processComponent(t.getName().replace("/", "."), visited); + } + } + } + + private void processSpringComponents(TypeSystem ts, URL springComponentsFile) { + Properties p = new Properties(); + loadSpringFactoryFile(springComponentsFile, p); + // Example: + // com.example.demo.Foobar=org.springframework.stereotype.Component + // com.example.demo.DemoApplication=org.springframework.stereotype.Component + Enumeration keys = p.keys(); + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + while (keys.hasMoreElements()) { + String k = (String)keys.nextElement(); + System.out.println("Registering Spring Component: "+k); + reflectionHandler.addAccess(k,Flag.allDeclaredConstructors, Flag.allDeclaredMethods, Flag.allDeclaredClasses); + resourcesRegistry.addResources(k.replace(".", "/")+".class"); + // Register nested types of the component + Type baseType = ts.resolveDotted(k); + for (Type t: baseType.getNestedTypes()) { + String n = t.getName().replace("/", "."); + reflectionHandler.addAccess(n,Flag.allDeclaredConstructors, Flag.allDeclaredMethods, Flag.allDeclaredClasses); + resourcesRegistry.addResources(t.getName()+".class"); + } + registerHierarchy(baseType, new HashSet<>(), resourcesRegistry); + } + } + + public void registerHierarchy(Type t, Set visited, ResourcesRegistry resourcesRegistry) { + if (t == null || t.getName().equals("java/lang/Object") || !visited.add(t)) { + return; + } + String desc = t.getName(); + System.out.println("Hierarchy registration of "+t.getName()); + reflectionHandler.addAccess(desc.replace("/", "."),Flag.allDeclaredConstructors, Flag.allDeclaredMethods, Flag.allDeclaredClasses); + resourcesRegistry.addResources(desc.replace("$", ".")+".class"); + Type s = t.getSuperclass(); + registerHierarchy(s, visited, resourcesRegistry); + Type[] is = t.getInterfaces(); + for (Type i: is) { + registerHierarchy(i, visited, resourcesRegistry); + } + // TODO inners of those supertypes/interfaces? + } + + /** + * Find all META-INF/spring.factories - for any configurations listed in each, check if those configurations use ConditionalOnClass. + * If the classes listed in ConditionalOnClass can't be found, discard the configuration from spring.factories. Register either + * the unchanged or modified spring.factories files with the system. + */ + public void processSpringFactories() { + log("Processing META-INF/spring.factories files..."); + Enumeration springFactories = fetchResources("META-INF/spring.factories"); + while (springFactories.hasMoreElements()) { + URL springFactory = springFactories.nextElement(); + processSpringFactory(ts, springFactory); + } + } + + + private List> filterComponents(List> as) { + List> filtered = new ArrayList<>(); + List> subtypesToRemove = new ArrayList<>(); + for (Entry a: as) { + String type = a.getKey(); + subtypesToRemove.addAll(as.stream().filter(e -> e.getKey().startsWith(type+"$")).collect(Collectors.toList())); + } + filtered.addAll(as); + filtered.removeAll(subtypesToRemove); + return filtered; + } + + private List> scanClasspathForIndexedStereotypes() { + return findDirectories(ts.getClasspath()) + .flatMap(this::findClasses) + .map(this::typenameOfClass) + .map(this::isIndexedOrEntity) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + +// app.main.SampleTransactionManagementConfiguration=org.springframework.stereotype.Component +// app.main.model.Foo=javax.persistence.Entity +// app.main.model.FooRepository=org.springframework.data.repository.Repository +// app.main.SampleApplication=org.springframework.stereotype.Component + private Entry isIndexedOrEntity(String slashedClassname) { + Entry entry = ts.resolveSlashed(slashedClassname).isIndexedOrEntity(); +// if (entry != null) { +// System.out.println("isIndexed for "+slashedClassname+" returned "+entry); +// } + return entry; + } + + private String typenameOfClass(File f) { + return Utils.scanClass(f).getClassname(); + } + + private Stream findClasses(File dir) { + ArrayList classfiles = new ArrayList<>(); + walk(dir,classfiles); + return classfiles.stream(); + } + + private void walk(File dir, ArrayList classfiles) { + File[] fs = dir.listFiles(); + for (File f: fs) { + if (f.isDirectory()) { + walk(f,classfiles); + } else if (f.getName().endsWith(".class")) { + classfiles.add(f); + } + } + } + + private Stream findDirectories(List classpath) { + List directories = new ArrayList<>(); + for (String classpathEntry: classpath) { + File f = new File(classpathEntry); + if (f.isDirectory()) { + directories.add(f); + } + } + return directories.stream(); + } + + private void processSpringFactory(TypeSystem ts, URL springFactory) { + List forRemoval = new ArrayList<>(); + Properties p = new Properties(); + loadSpringFactoryFile(springFactory, p); + + Enumeration keyz = p.keys(); + // Handle all keys except EnableAutoConfiguration + while (keyz.hasMoreElements()) { + String k = (String)keyz.nextElement(); + if (!k.equals("org.springframework.boot.autoconfigure.EnableAutoConfiguration")) { + String classesList = p.getProperty(k); + for (String s: classesList.split(",")) { + try { + reflectionHandler.addAccess(s,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + System.out.println("NEEDS ADDING TO RESOURCE LIST? "+s); + } catch (NoClassDefFoundError ncdfe) { + System.out.println("SBG: WARNING: Whilst processing "+k+" problem adding access for type: "+s+" because of missing "+ncdfe.getMessage()); + } + } + } + } + + String configsString = (String) p.get("org.springframework.boot.autoconfigure.EnableAutoConfiguration"); + if (configsString != null) { + List configs = new ArrayList<>(); + for (String s: configsString.split(",")) { + configs.add(s); + } + // TODO what about ConditionalOnResource? + System.out.println( + "Spring.factories processing: looking at #" + configs.size() + " configuration references"); + for (Iterator iterator = configs.iterator(); iterator.hasNext();) { + String config = iterator.next(); + boolean needToAddThem = true; + if (!verifyType(config)) { + System.out.println("Excluding auto-configuration " + config); + System.out.println("= COC failed so just adding class forname access (no methods/ctors)"); + if (REMOVE_UNNECESSARY_CONFIGURATIONS) { + forRemoval.add(config); + needToAddThem = false; + } + } + if (needToAddThem) { + System.out.println("Resource Adding: "+config); + reflectionHandler.addAccess(config); // no flags as it isn't going to trigger + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + resourcesRegistry.addResources(config.replace(".", "/").replace("$", ".")+".class"); + } + } + configs.removeAll(forRemoval); + p.put("org.springframework.boot.autoconfigure.EnableAutoConfiguration", String.join(",", configs)); + } + try { + if (forRemoval.size() == 0) { + Resources.registerResource("META-INF/spring.factories", springFactory.openStream()); + } else { + System.out.println(" removed " + forRemoval.size() + " configurations"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + p.store(baos,""); + baos.close(); + byte[] bs = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bs); + Resources.registerResource("META-INF/spring.factories", bais); + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private void loadSpringFactoryFile(URL springFactory, Properties p) { + try (InputStream is = springFactory.openStream()) { + p.load(is); + } catch (IOException e) { + throw new IllegalStateException("Unable to load spring.factories", e); + } + } + + /** + * For the specified type (dotted name) determine which types must be reflectable at runtime. This means + * looking at annotations and following any type references within those. + */ + private boolean verifyType(String name) { + return processType(name, new HashSet<>()); + } + + private boolean processType(String config, Set visited) { + return processType(ts.resolveDotted(config), visited, 0); + } + + private boolean processType(Type configType, Set visited, int depth) { + System.out.println(spaces(depth)+"Processing type "+configType.getName()); + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + + // This would fetch 'things we care about from a graal point of view' + // a list + // ConditionalOnClass (annotation instance) + // configType.getRelevantAnnotations(); + // Then for each of those give me the relevant 'types i have to look around for' + + + Set missing = ts.resolveCompleteFindMissingTypes(configType); + if (!missing.isEmpty()) { + // No point continuing with this type, it cannot be resolved against current classpath + // The assumption is it will never need to be accessed anyway + System.out.println(spaces(depth)+"for "+configType.getName()+" missing types are "+missing); + return false; + } + + Set missingAnnotationTypes = ts.resolveCompleteFindMissingAnnotationTypes(configType); + if (!missingAnnotationTypes.isEmpty()) { + // If only the annotations are missing, it is ok to reflect on the existence of the type, it is + // just not safe to reflect on the annotations on that type. + System.out.println(spaces(depth)+"for "+configType.getName()+" missing annotation types are "+missingAnnotationTypes); + } + boolean passesTests = true; + Set toMakeAccessible = new HashSet<>(); + Map> hints = configType.getHints(); + if (!hints.isEmpty()) { + int h=1; + for (Map.Entry> hint: hints.entrySet()) { + HintDescriptor hintDescriptor = hint.getKey(); + List typeReferences = hint.getValue(); + System.out.println(spaces(depth)+"checking @CompilationHint "+h+"/"+hints.size()+" "+hintDescriptor.getAnnotationChain()); + + String[] name = hintDescriptor.getName(); + if (name != null) { + // The CollectionHint included a list of types to worry about in the annotation + // itself (e.g. as used on import selector to specify. + for (String n: name) { + resourcesRegistry.addResources(n.replace(".", "/")+".class"); + reflectionHandler.addAccess(n,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + } + } + + if (h==1) { // only do this once all hints should have this in common, TODO polish this up... + // This handles the case for something like: + // ReactiveWebServerFactoryConfiguration$EmbeddedTomcat with ConditionalOnClass + // TODO is this too much repetition for certain types? + for (Type annotatedType : hintDescriptor.getAnnotationChain()) { + try { + System.out.println("Handling annotated thingy: "+annotatedType.getName()); + String t = annotatedType.getDescriptor(); + reflectionHandler.addAccess(t.substring(1,t.length()-1).replace("/", "."),Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(t.substring(1,t.length()-1).replace("$", ".")+".class"); + } catch (NoClassDefFoundError e) { + System.out.println(spaces(depth)+annotatedType.getName()+" not found for configuration "+configType.getName()); + } + + } + } + + if (typeReferences != null) { + for (String typeReference: typeReferences) { // La/b/C; + Type t = ts.Lresolve(typeReference, true); + boolean exists = (t != null); + System.out.println(spaces(depth)+" does "+fromLtoDotted(typeReference)+" exist? "+exists); + if (exists) { + // TODO should this specify what aspects of reflection are required (methods/fields/ctors/annotations) + toMakeAccessible.add(typeReference); + if (hintDescriptor.isFollow()) { + processType(t, visited, depth+1); + } + } else if (hintDescriptor.isSkipIfTypesMissing()) { + passesTests = false; + } + } + } + h++; + } + } + + if (passesTests || !REMOVE_UNNECESSARY_CONFIGURATIONS) { + for (String t: toMakeAccessible) { + try { + reflectionHandler.addAccess(t.substring(1,t.length()-1).replace("/", "."),Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(t.substring(1,t.length()-1).replace("$", ".")+".class"); + } catch (NoClassDefFoundError e) { + System.out.println(spaces(depth)+"Conditional type "+fromLtoDotted(t)+" not found for configuration "+configType.getName()); + } + } + } + + if (passesTests) { + try { + String configNameDotted = configType.getName().replace("/","."); + System.out.println(spaces(depth)+"including reflective/resource access to "+configNameDotted); + visited.add(configType.getName()); + reflectionHandler.addAccess(configNameDotted,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + System.out.println("res: "+configType.getName().replace("$", ".")+".class"); + resourcesRegistry.addResources(configType.getName().replace("$", ".")+".class"); + // In some cases the superclass of the config needs to be accessible + // TODO need this guard? if (isConfiguration(configType)) { + registerHierarchy(configType, new HashSet<>(), resourcesRegistry); + } catch (NoClassDefFoundError e) { + // Example: + // PROBLEM? Can't register Type:org/springframework/boot/autoconfigure/web/servlet/HttpEncodingAutoConfiguration because cannot find javax/servlet/Filter + // java.lang.NoClassDefFoundError: javax/servlet/Filter + // ... at com.oracle.svm.hosted.config.ReflectionRegistryAdapter.registerDeclaredConstructors(ReflectionRegistryAdapter.java:97) + System.out.println("PROBLEM? Can't register "+configType.getName()+" because cannot find "+e.getMessage()); + } + } + + // HibernateJpaConfiguration has a supertype also covered with @Configuration - so more than just registering + // the hierarchy as accessible, it may contain more config to chase down + Type s = configType.getSuperclass(); + while (s!= null) { + processType(s, visited, depth+1); + s = s.getSuperclass(); + } + + // If the outer type is failing a test, we don't need to recurse... + if (passesTests) { + List nestedTypes = configType.getNestedTypes(); + for (Type t: nestedTypes) { + if (visited.add(t.getName())) { + processType(t, visited, depth+1); + } + } + } else { + System.out.println("INFO: tests failed on "+configType.getName()+" so not going into nested types"); + } + return passesTests; + } + + String fromLtoDotted(String lDescriptor) { + return lDescriptor.substring(1,lDescriptor.length()-1).replace("/", "."); + } + + private Enumeration fetchResources(String resource) { + try { + Enumeration resources = Thread.currentThread().getContextClassLoader().getResources(resource); + return resources; + } catch (IOException e1) { + return Collections.enumeration(Collections.emptyList()); + } + } + + private void log(String msg) { + System.out.println(msg); + } + + private String spaces(int depth) { + return " ".substring(0,depth*2); + } + +} diff --git a/src/main/java/org/springframework/graal/support/SpringFeature.java b/src/main/java/org/springframework/graal/support/SpringFeature.java new file mode 100644 index 000000000..a7a2a1d0c --- /dev/null +++ b/src/main/java/org/springframework/graal/support/SpringFeature.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.support; + +import java.nio.Buffer; +import java.util.ArrayList; +import java.util.List; + +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.hosted.ResourcesFeature; +import com.oracle.svm.reflect.hosted.ReflectionFeature; +import com.oracle.svm.reflect.proxy.hosted.DynamicProxyFeature; + +@AutomaticFeature +public class SpringFeature implements Feature { + + private ReflectionHandler reflectionHandler; + + private DynamicProxiesHandler dynamicProxiesHandler; + + private ResourcesHandler resourcesHandler; + + private InitializationHandler buildTimeInitializationHandler; + + public SpringFeature() { + System.out.println( + " ____ _ _____ _ \n"+ + "/ ___| _ __ _ __(_)_ __ __ _ | ___|__ __ _| |_ _ _ _ __ ___ \n"+ + "\\___ \\| '_ \\| '__| | '_ \\ / _` | | |_ / _ \\/ _` | __| | | | '__/ _ \\\n"+ + " ___) | |_) | | | | | | | (_| | | _| __/ (_| | |_| |_| | | | __/\n"+ + "|____/| .__/|_| |_|_| |_|\\__, | |_| \\___|\\__,_|\\__|\\__,_|_| \\___|\n"+ + " |_| |___/ \n"); + reflectionHandler = new ReflectionHandler(); + dynamicProxiesHandler = new DynamicProxiesHandler(); + resourcesHandler = new ResourcesHandler(reflectionHandler); + buildTimeInitializationHandler = new InitializationHandler(); + } + + public boolean isInConfiguration(IsInConfigurationAccess access) { + return true; + } + + public List> getRequiredFeatures() { + List> fs = new ArrayList<>(); + fs.add(DynamicProxyFeature.class); // Ensures DynamicProxyRegistry available + fs.add(ResourcesFeature.class); // Ensures ResourcesRegistry available + fs.add(ReflectionFeature.class); // Ensures RuntimeReflectionSupport available + return fs; + } + + public void duringSetup(DuringSetupAccess access) { + reflectionHandler.register(access); + dynamicProxiesHandler.register(access); + } + + public void beforeAnalysis(BeforeAnalysisAccess access) { + resourcesHandler.register(access); + buildTimeInitializationHandler.register(access); + // TODO who requires this, is it a netty thing? + try { + access.registerAsUnsafeAccessed(Buffer.class.getDeclaredField("address")); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/org/springframework/graal/support/Utils.java b/src/main/java/org/springframework/graal/support/Utils.java new file mode 100644 index 000000000..6a9414426 --- /dev/null +++ b/src/main/java/org/springframework/graal/support/Utils.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.support; + +import java.io.File; + +import org.springframework.graal.type.ConstantPoolScanner; + +public class Utils { + + public static ConstantPoolScanner scanClass(File f) { + return new ConstantPoolScanner(f); + } + +} diff --git a/src/main/java/org/springframework/graal/type/CompilationHint.java b/src/main/java/org/springframework/graal/type/CompilationHint.java new file mode 100644 index 000000000..cbc9db563 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/CompilationHint.java @@ -0,0 +1,27 @@ +package org.springframework.graal.type; + +public @interface CompilationHint { + + // When attached to an annotation, this indicates which fields in that annotation + // hold type references, e.g. if on ConditionalOnClass, names={"value","name"} + String[] fieldNames(); + + // If difficult to tell the types involved from the thing being annotated, the info can be put here + // (e.g. you have an ImportSelector returning classnames, the possible names should be in here) + String[] name(); + Class[] value(); + + // If true then whatever class is annotated/meta-annotateed with this is useless if + // the types visible through the names() fields are not found. + boolean skipIfTypesMissing(); + + // If true, then whatever types are referenced need to be followed because they may + // be further annotated/meta-annotated with compilation hints + boolean follow(); + + // Do we need to specify what reflection should be accessible? (Fields/Methods/Ctors)? + // Reducing the amount could likely help the image size + + // If true, whatever is (meta-)annotated with this must be accessible via getResource too. + boolean accessibleAsResource(); +} diff --git a/src/main/java/org/springframework/graal/type/ConstantPoolScanner.java b/src/main/java/org/springframework/graal/type/ConstantPoolScanner.java new file mode 100644 index 000000000..20fc7325a --- /dev/null +++ b/src/main/java/org/springframework/graal/type/ConstantPoolScanner.java @@ -0,0 +1,336 @@ +/* + * Copyright 2019 Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.type; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Enables us to check things quickly in the constant pool. Just parses the class up to the end of the constant pool. + * + * Useful reference: https://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html + * + * @author Andy Clement + */ +public class ConstantPoolScanner { + + private static final boolean DEBUG = false; + + private final static byte CONSTANT_Utf8 = 1; + + private final static byte CONSTANT_Integer = 3; + + private final static byte CONSTANT_Float = 4; + + private final static byte CONSTANT_Long = 5; + + private final static byte CONSTANT_Double = 6; + + private final static byte CONSTANT_Class = 7; + + private final static byte CONSTANT_String = 8; + + private final static byte CONSTANT_Fieldref = 9; + + private final static byte CONSTANT_Methodref = 10; + + private final static byte CONSTANT_InterfaceMethodref = 11; + + private final static byte CONSTANT_NameAndType = 12; + + private final static byte CONSTANT_MethodHandle = 15; + + private final static byte CONSTANT_MethodType = 16; + + private final static byte CONSTANT_InvokeDynamic = 18; + + private byte[] classbytes; + + // Used during the parse step + private int ptr; + + // Filled with strings and int[] + private Object[] cpdata; + + private int cpsize; + + private int[] type; + + // Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...) + private List referencedClasses = new ArrayList(); + + private List referencedMethods = new ArrayList(); + + private String slashedclassname; + + + public static References getReferences(byte[] classbytes) { + ConstantPoolScanner cpScanner = new ConstantPoolScanner(classbytes); + return new References(cpScanner.slashedclassname, cpScanner.referencedClasses, cpScanner.referencedMethods); + } + + public static References getReferences(File f) { + try { + byte[] bytes = Files.readAllBytes(Paths.get(f.toURI())); + ConstantPoolScanner cpScanner = new ConstantPoolScanner(bytes); + return new References(cpScanner.slashedclassname, cpScanner.referencedClasses, cpScanner.referencedMethods); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public ConstantPoolScanner(File f) { + this(readBytes(f)); + } + + public static byte[] readBytes(File f) { + try { + byte[] bytes = Files.readAllBytes(Paths.get(f.toURI())); + return bytes; + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private ConstantPoolScanner(byte[] bytes) { + parseClass(bytes); +// computeReferences(); + } + + // Format of a classfile: + // ClassFile { + // u4 magic; + // u2 minor_version; + // u2 major_version; + // u2 constant_pool_count; + // cp_info constant_pool[constant_pool_count-1]; + // u2 access_flags; + // u2 this_class; + // u2 super_class; + // u2 interfaces_count; + // u2 interfaces[interfaces_count]; + // u2 fields_count; + // field_info fields[fields_count]; + // u2 methods_count; + // method_info methods[methods_count]; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + private void parseClass(byte[] bytes) { + try { + this.classbytes = bytes; + this.ptr = 0; + int magic = readInt(); // magic 0xCAFEBABE + if (magic != 0xCAFEBABE) { + throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16)); + } + ptr += 4; // skip minor and major versions + cpsize = readUnsignedShort(); + if (DEBUG) { + System.out.println("Constant Pool Size =" + cpsize); + } + cpdata = new Object[cpsize]; + type = new int[cpsize]; + for (int cpentry = 1; cpentry < cpsize; cpentry++) { + boolean wasDoubleSlotItem = processConstantPoolEntry(cpentry); + if (wasDoubleSlotItem) { + cpentry++; + } + } + ptr += 2; // access flags + int thisclassname = readUnsignedShort(); + int classindex = ((Integer) cpdata[thisclassname]); + slashedclassname = accessUtf8(classindex); + } + catch (Exception e) { + throw new IllegalStateException("Unexpected problem processing bytes for class", e); + } + } + + public String getClassname() { + return slashedclassname; + } + + /** + * Return the UTF8 at the specified index in the constant pool. The data found at the constant pool for that index + * may not have been unpacked yet if this is the first access of the string. If not unpacked the constant pool entry + * is a pair of ints in an array representing the offset and length within the classbytes where the UTF8 string is + * encoded. Once decoded the constant pool entry is flipped from an int array to a String for future fast access. + * + * @param cpIndex constant pool index + * @return UTF8 string at that constant pool index + */ + private String accessUtf8(int cpIndex) { + Object object = cpdata[cpIndex]; + if (object instanceof String) { + return (String) object; + } + int[] ptrAndLen = (int[]) object; + String value; + try { + value = new String(classbytes, ptrAndLen[0], ptrAndLen[1], "UTF8"); + } + catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Bad data found at constant pool position " + cpIndex + " offset=" + + ptrAndLen[0] + " length=" + ptrAndLen[1], e); + } + cpdata[cpIndex] = value; + return value; + } + + /** + * @return an int constructed from the next four bytes to be processed + */ + private final int readInt() { + return ((classbytes[ptr++] & 0xFF) << 24) + ((classbytes[ptr++] & 0xFF) << 16) + + ((classbytes[ptr++] & 0xFF) << 8) + + (classbytes[ptr++] & 0xFF); + } + + /** + * @return an unsigned short constructed from the next two bytes to be processed + */ + private final int readUnsignedShort() { + return ((classbytes[ptr++] & 0xff) << 8) + (classbytes[ptr++] & 0xff); + } + + private boolean processConstantPoolEntry(int index) throws IOException { + byte b = classbytes[ptr++]; + switch (b) { + case CONSTANT_Integer: // CONSTANT_Integer_info { u1 tag; u4 bytes; } + case CONSTANT_Float: // CONSTANT_Float_info { u1 tag; u4 bytes; } + case CONSTANT_Fieldref: // CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } + case CONSTANT_InterfaceMethodref: // CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } + case CONSTANT_InvokeDynamic: // CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; } + ptr += 4; + break; + case CONSTANT_Utf8: + // CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; } + // Cache just the index and length - do not unpack it now + int len = readUnsignedShort(); + cpdata[index] = new int[] { ptr, len }; + ptr += len; + break; + case CONSTANT_Long: // CONSTANT_Long_info { u1 tag; u4 high_bytes; u4 low_bytes; } + case CONSTANT_Double: // CONSTANT_Double_info { u1 tag; u4 high_bytes; u4 low_bytes; } + ptr += 8; + return true; + case CONSTANT_Class: // CONSTANT_Class_info { u1 tag; u2 name_index; } + type[index] = b; + cpdata[index] = readUnsignedShort(); + break; + case CONSTANT_Methodref: + // CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } + type[index] = b; + cpdata[index] = new int[] { readUnsignedShort(), readUnsignedShort() }; + break; + case CONSTANT_NameAndType: + // The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to: + // CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; } + // type[index] = b; + cpdata[index] = readUnsignedShort(); + ptr += 2; // skip the descriptor for now + break; + case CONSTANT_MethodHandle: + // CONSTANT_MethodHandle_info { u1 tag; u1 reference_kind; u2 reference_index; } + ptr += 3; + break; + case CONSTANT_String: // CONSTANT_String_info { u1 tag; u2 string_index; } + case CONSTANT_MethodType: // CONSTANT_MethodType_info { u1 tag; u2 descriptor_index; } + ptr += 2; + break; + default: + throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b)); + } + return false; + } + + private void computeReferences() { + for (int i = 0; i < cpsize; i++) { + switch (type[i]) { + case CONSTANT_Class: + int classindex = ((Integer) cpdata[i]); + String classname = accessUtf8(classindex); + if (classname == null) { + throw new IllegalStateException(); + } + referencedClasses.add(classname); + break; + case CONSTANT_Methodref: + int[] indexes = (int[]) cpdata[i]; + int classindex2 = indexes[0]; + int nameAndTypeIndex = indexes[1]; + StringBuilder s = new StringBuilder(); + String theClassName = accessUtf8((Integer) cpdata[classindex2]); + if (theClassName.charAt(0) == 'j') { + s.append(theClassName); + s.append("."); + s.append(accessUtf8((Integer) cpdata[nameAndTypeIndex])); + referencedMethods.add(s.toString()); + } + break; + // private final static byte CONSTANT_Utf8 = 1; + // private final static byte CONSTANT_Integer = 3; + // private final static byte CONSTANT_Float = 4; + // private final static byte CONSTANT_Long = 5; + // private final static byte CONSTANT_Double = 6; + // private final static byte CONSTANT_String = 8; + // private final static byte CONSTANT_Fieldref = 9; + // private final static byte CONSTANT_InterfaceMethodref = 11; + // private final static byte CONSTANT_NameAndType = 12; + } + } + } + + + public static class References { + + public final String slashedClassName; + + private final List referencedClasses; + + private final List referencedMethods; + + References(String slashedClassName, List rc, List rm) { + this.slashedClassName = slashedClassName; + this.referencedClasses = rc; + this.referencedMethods = rm; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Class=").append(slashedClassName).append("\n"); + s.append("ReferencedClasses=#").append(referencedClasses.size()).append("\n"); + s.append("ReferencedMethods=#").append(referencedMethods.size()).append("\n"); + return s.toString(); + } + + /** + * @return list of classes of the form org/springframework/boot/configurationprocessor/json/JSONException + */ + public List getReferencedClasses() { + return referencedClasses; + } + } + + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/HintDescriptor.java b/src/main/java/org/springframework/graal/type/HintDescriptor.java new file mode 100644 index 000000000..7b4765e3a --- /dev/null +++ b/src/main/java/org/springframework/graal/type/HintDescriptor.java @@ -0,0 +1,51 @@ +package org.springframework.graal.type; + +import java.util.List; + +/** + * Represents a usage of @CompilationHint. + * + * @author Andy Clement + */ +public class HintDescriptor { + + // This is the annotation 'chain' from the type that got asked about to the thing with @CompilationHint + // This chain may be short (e.g. if an autoconfig has @ConditionalOnClass on it which itself + // is meta annotated with @CompilationHint): chain will be [ConditionalOnClass] + // or it may be long: (e.g. if the autoconfig has an @EnableFoo on it which itself is marked + // with @ConditionalOnClass which in turn has CompilationHint) chain will be [EnableFoo, ConditionalOnClass] + private List annotationChain; + + // If any types hinted at are missing, is this type effectively redundant? + private boolean skipIfTypesMissing; + + // Should any types references be followed because they may also have further + // hints on them (e.g. @Import(Foo) where Foo has @Import(Bar) on it) + private boolean follow; + + private String[] name; + + public HintDescriptor(List annotationChain, boolean skipIfTypesMissing2, boolean follow, String[] name) { + this.annotationChain = annotationChain; + this.skipIfTypesMissing = skipIfTypesMissing2; + this.follow = follow; + this.name = name; + } + + public List getAnnotationChain() { + return annotationChain; + } + + public boolean isSkipIfTypesMissing() { + return skipIfTypesMissing; + } + + public boolean isFollow() { + return follow; + } + + public String[] getName() { + return name; + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/Method.java b/src/main/java/org/springframework/graal/type/Method.java new file mode 100644 index 000000000..8bb046d77 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/Method.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graal.type; + +import org.objectweb.asm.tree.MethodNode; + +public class Method { + + MethodNode mn; + + public Method(MethodNode mn) { + this.mn = mn; + } + + public String toString() { + return mn.name+mn.desc; + } + + public String getName() { + return mn.name; + } + + public String getDesc() { + return mn.desc; + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/MissingTypeException.java b/src/main/java/org/springframework/graal/type/MissingTypeException.java new file mode 100644 index 000000000..e63af7f94 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/MissingTypeException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.type; + +public class MissingTypeException extends RuntimeException { + private static final long serialVersionUID = 1L; + private String typename; + + public MissingTypeException(String slashedTypeName) { + this.typename = slashedTypeName; + } + + @Override + public String getMessage() { + return "Unable to find class file for " + typename; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/Type.java b/src/main/java/org/springframework/graal/type/Type.java new file mode 100644 index 000000000..36eb3dbe1 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/Type.java @@ -0,0 +1,1074 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.type; + +import java.lang.reflect.Modifier; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; +import java.util.stream.Collectors; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InnerClassNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * @author Andy Clement + */ +public class Type { + + // Spring Security + public final static String OAuth2ImportSelector = "Lorg/springframework/security/config/annotation/web/configuration/OAuth2ImportSelector;"; + + public final static String SpringWebMvcImportSelector = "Lorg/springframework/security/config/annotation/web/configuration/SpringWebMvcImportSelector;"; + + public final static String ImportSelector ="Lorg/springframework/context/annotation/ImportSelector;"; + + public final static String TransactionManagementConfigurationSelector = "Lorg/springframework/transaction/annotation/TransactionManagementConfigurationSelector;"; + + public final static String SpringDataWebConfigurationSelector = "Lorg/springframework/data/web/config/EnableSpringDataWebSupport$SpringDataWebConfigurationImportSelector;"; + + public final static String SpringDataWebQueryDslSelector = "Lorg/springframework/data/web/config/EnableSpringDataWebSupport$QuerydslActivator;"; + + public final static String AdviceModeImportSelector="Lorg/springframework/context/annotation/AdviceModeImportSelector;"; + + public final static String EnableConfigurationPropertiesImportSelector = "Lorg/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector;"; + + public final static String CacheConfigurationImportSelector = "Lorg/springframework/boot/autoconfigure/cache/CacheAutoConfiguration$CacheConfigurationImportSelector;"; + + public final static String RabbitConfigurationImportSelector = "Lorg/springframework/amqp/rabbit/annotation/RabbitListenerConfigurationSelector;"; + + public final static String AtBean = "Lorg/springframework/context/annotation/Bean;"; + + public final static String AtImports = "Lorg/springframework/context/annotation/Import;"; + + public final static String AtEnableConfigurationProperties = "Lorg/springframework/boot/context/properties/EnableConfigurationProperties;"; + + public final static String AtConditionalOnClass = "Lorg/springframework/boot/autoconfigure/condition/ConditionalOnClass;"; + + public final static String AtConditional = "Lorg/springframework/context/annotation/Conditional;"; + + public final static String AtConditionalOnMissingBean = "Lorg/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean;"; + + public final static String HypermediaConfigurationImportSelector = "Lorg/springframework/hateoas/config/HypermediaConfigurationImportSelector;"; + + public final static String WebStackImportSelector = "Lorg/springframework/hateoas/config/WebStackImportSelector;"; + + public final static Type MISSING = new Type(null, null); + + public final static Type[] NO_INTERFACES = new Type[0]; + + protected static Set validBoxing = new HashSet(); + + + private TypeSystem typeSystem; + + private ClassNode node; + + private Type[] interfaces; + + public Type(TypeSystem typeSystem, ClassNode node) { + this.typeSystem = typeSystem; + this.node = node; + } + + public static Type forClassNode(TypeSystem typeSystem, ClassNode node) { + return new Type(typeSystem, node); + } + + /** + * @return typename in slashed form (aaa/bbb/ccc/Ddd$Eee) + */ + public String getName() { + return node.name; + } + + public String getDottedName() { + return node.name.replace("/", "."); + } + + public Type getSuperclass() { + if (node.superName == null) { + return null; + } + return typeSystem.resolveSlashed(node.superName); + } + + @Override + public String toString() { + return "Type:"+getName(); + } + + public Type[] getInterfaces() { + if (interfaces == null) { + List itfs = node.interfaces; + if (itfs.size() == 0) { + interfaces = NO_INTERFACES; + } else { + interfaces = new Type[itfs.size()]; + for (int i = 0; i < itfs.size(); i++) { + interfaces[i] = typeSystem.resolveSlashed(itfs.get(i)); + } + } + } + return interfaces; + } + + /** @return List of slashed interface types */ + public List getInterfacesStrings() { + return node.interfaces; + } + + /** @return slashed supertype name */ + public String getSuperclassString() { + return node.superName; + } + + public List getTypesInSignature() { + if (node.signature == null) { + return Collections.emptyList(); + } else { + SignatureReader reader = new SignatureReader(node.signature); + TypeCollector tc = new TypeCollector(); + reader.accept(tc); + return tc.getTypes(); + } + } + + static class TypeCollector extends SignatureVisitor { + + List types = null; + + public TypeCollector() { + super(Opcodes.ASM7); + } + + @Override + public void visitClassType(String name) { + if (types == null) { + types = new ArrayList(); + } + types.add(name); + } + + public List getTypes() { + if (types == null) { + return Collections.emptyList(); + } else { + return types; + } + } + + } + + public boolean implementsInterface(String interfaceName) { + Type[] interfacesToCheck = getInterfaces(); + for (Type interfaceToCheck : interfacesToCheck) { + if (interfaceToCheck.getName().equals(interfaceName)) { + return true; + } + if (interfaceToCheck.implementsInterface(interfaceName)) { + return true; + } + } + Type superclass = getSuperclass(); + while (superclass != null) { + if (superclass.implementsInterface(interfaceName)) { + return true; + } + superclass = superclass.getSuperclass(); + } + return false; + } + + public List getMethodsWithAnnotation(String string) { + return node.methods.stream().filter(m -> hasAnnotation(m, string)).map(m -> wrap(m)) + .collect(Collectors.toList()); + } + + public List getMethodsWithAtBean() { + return getMethodsWithAnnotation(AtBean); + } + + public Method wrap(MethodNode mn) { + return new Method(mn); + } + + private boolean hasAnnotation(MethodNode m, String string) { + List visibleAnnotations = m.visibleAnnotations; + Optional findAny = visibleAnnotations == null ? Optional.empty() + : visibleAnnotations.stream().filter(a -> a.desc.equals(string)).findFirst(); + return findAny.isPresent(); + } + + static { + validBoxing.add("Ljava/lang/Byte;B"); + validBoxing.add("Ljava/lang/Character;C"); + validBoxing.add("Ljava/lang/Double;D"); + validBoxing.add("Ljava/lang/Float;F"); + validBoxing.add("Ljava/lang/Integer;I"); + validBoxing.add("Ljava/lang/Long;J"); + validBoxing.add("Ljava/lang/Short;S"); + validBoxing.add("Ljava/lang/Boolean;Z"); + validBoxing.add("BLjava/lang/Byte;"); + validBoxing.add("CLjava/lang/Character;"); + validBoxing.add("DLjava/lang/Double;"); + validBoxing.add("FLjava/lang/Float;"); + validBoxing.add("ILjava/lang/Integer;"); + validBoxing.add("JLjava/lang/Long;"); + validBoxing.add("SLjava/lang/Short;"); + validBoxing.add("ZLjava/lang/Boolean;"); + } + + public boolean isAssignableFrom(Type other) { + if (other.isPrimitiveType()) { + if (validBoxing.contains(this.getName() + other.getName())) { + return true; + } + } + if (this == other) { + return true; + } + + if (this.getName().equals("Ljava/lang/Object;")) { + return true; + } + +// if (!isTypeVariableReference() +// && other.getSignature().equals("Ljava/lang/Object;")) { +// return false; +// } + +// boolean thisRaw = this.isRawType(); +// if (thisRaw && other.isParameterizedOrGenericType()) { +// return isAssignableFrom(other.getRawType()); +// } +// +// boolean thisGeneric = this.isGenericType(); +// if (thisGeneric && other.isParameterizedOrRawType()) { +// return isAssignableFrom(other.getGenericType()); +// } +// +// if (this.isParameterizedType()) { +// // look at wildcards... +// if (((ReferenceType) this.getRawType()).isAssignableFrom(other)) { +// boolean wildcardsAllTheWay = true; +// ResolvedType[] myParameters = this.getResolvedTypeParameters(); +// for (int i = 0; i < myParameters.length; i++) { +// if (!myParameters[i].isGenericWildcard()) { +// wildcardsAllTheWay = false; +// } else { +// BoundedReferenceType boundedRT = (BoundedReferenceType) myParameters[i]; +// if (boundedRT.isExtends() || boundedRT.isSuper()) { +// wildcardsAllTheWay = false; +// } +// } +// } +// if (wildcardsAllTheWay && !other.isParameterizedType()) { +// return true; +// } +// // we have to match by parameters one at a time +// ResolvedType[] theirParameters = other +// .getResolvedTypeParameters(); +// boolean parametersAssignable = true; +// if (myParameters.length == theirParameters.length) { +// for (int i = 0; i < myParameters.length +// && parametersAssignable; i++) { +// if (myParameters[i] == theirParameters[i]) { +// continue; +// } +// // dont do this: pr253109 +// // if +// // (myParameters[i].isAssignableFrom(theirParameters[i], +// // allowMissing)) { +// // continue; +// // } +// ResolvedType mp = myParameters[i]; +// ResolvedType tp = theirParameters[i]; +// if (mp.isParameterizedType() +// && tp.isParameterizedType()) { +// if (mp.getGenericType().equals(tp.getGenericType())) { +// UnresolvedType[] mtps = mp.getTypeParameters(); +// UnresolvedType[] ttps = tp.getTypeParameters(); +// for (int ii = 0; ii < mtps.length; ii++) { +// if (mtps[ii].isTypeVariableReference() +// && ttps[ii] +// .isTypeVariableReference()) { +// TypeVariable mtv = ((TypeVariableReferenceType) mtps[ii]) +// .getTypeVariable(); +// boolean b = mtv +// .canBeBoundTo((ResolvedType) ttps[ii]); +// if (!b) {// TODO incomplete testing here +// // I think +// parametersAssignable = false; +// break; +// } +// } else { +// parametersAssignable = false; +// break; +// } +// } +// continue; +// } else { +// parametersAssignable = false; +// break; +// } +// } +// if (myParameters[i].isTypeVariableReference() +// && theirParameters[i].isTypeVariableReference()) { +// TypeVariable myTV = ((TypeVariableReferenceType) myParameters[i]) +// .getTypeVariable(); +// // TypeVariable theirTV = +// // ((TypeVariableReferenceType) +// // theirParameters[i]).getTypeVariable(); +// boolean b = myTV.canBeBoundTo(theirParameters[i]); +// if (!b) {// TODO incomplete testing here I think +// parametersAssignable = false; +// break; +// } else { +// continue; +// } +// } +// if (!myParameters[i].isGenericWildcard()) { +// parametersAssignable = false; +// break; +// } else { +// BoundedReferenceType wildcardType = (BoundedReferenceType) myParameters[i]; +// if (!wildcardType.alwaysMatches(theirParameters[i])) { +// parametersAssignable = false; +// break; +// } +// } +// } +// } else { +// parametersAssignable = false; +// } +// if (parametersAssignable) { +// return true; +// } +// } +// } +// +// // eg this=T other=Ljava/lang/Object; +// if (isTypeVariableReference() && !other.isTypeVariableReference()) { +// TypeVariable aVar = ((TypeVariableReference) this) +// .getTypeVariable(); +// return aVar.resolve(world).canBeBoundTo(other); +// } +// +// if (other.isTypeVariableReference()) { +// TypeVariableReferenceType otherType = (TypeVariableReferenceType) other; +// if (this instanceof TypeVariableReference) { +// return ((TypeVariableReference) this) +// .getTypeVariable() +// .resolve(world) +// .canBeBoundTo( +// otherType.getTypeVariable().getFirstBound() +// .resolve(world));// pr171952 +// // return +// // ((TypeVariableReference)this).getTypeVariable()==otherType +// // .getTypeVariable(); +// } else { +// // FIXME asc should this say canBeBoundTo?? +// return this.isAssignableFrom(otherType.getTypeVariable() +// .getFirstBound().resolve(world)); +// } +// } + + Type[] interfaces = other.getInterfaces(); + for (Type intface : interfaces) { + boolean b; +// if (thisRaw && intface.isParameterizedOrGenericType()) { +// b = this.isAssignableFrom(intface.getRawType(), allowMissing); +// } else { + b = this.isAssignableFrom(intface); +// } + if (b) { + return true; + } + } + Type superclass = other.getSuperclass(); + if (superclass != null) { + boolean b; +// if (thisRaw && superclass.isParameterizedOrGenericType()) { +// b = this.isAssignableFrom(superclass.getRawType(), allowMissing); +// } else { + b = this.isAssignableFrom(superclass); +// } + if (b) { + return true; + } + } + return false; + } + + private boolean isPrimitiveType() { + return false; + } + + public boolean isInterface() { + return Modifier.isInterface(node.access); + } + + public boolean hasAnnotationInHierarchy(String lookingFor) { + return hasAnnotationInHierarchy(lookingFor, new ArrayList()); + } + + public boolean hasAnnotationInHierarchy(String lookingFor, List seen) { + if (node.visibleAnnotations != null) { + for (AnnotationNode anno : node.visibleAnnotations) { + if (seen.contains(anno.desc)) + continue; + seen.add(anno.desc); + // System.out.println("Comparing "+anno.desc+" with "+lookingFor); + if (anno.desc.equals(lookingFor)) { + return true; + } + try { + Type resolve = typeSystem.Lresolve(anno.desc); + if (resolve.hasAnnotationInHierarchy(lookingFor, seen)) { + return true; + } + } catch (MissingTypeException mte) { + // not on classpath, that's ok + } + } + } + return false; + } + + public boolean isMetaAnnotated(String slashedTypeDescriptor) { + return isMetaAnnotated(slashedTypeDescriptor, new HashSet<>()); + } + + public boolean isMetaAnnotated(String slashedTypeDescriptor, Set seen) { +// System.out.println("Looking at "+this.getName()+" for "+slashedTypeDescriptor); + for (Type t : this.getAnnotations()) { + String tname = t.getName(); + if (tname.equals(slashedTypeDescriptor)) { + return true; + } + if (!seen.contains(tname)) { + seen.add(tname); + if (t.isMetaAnnotated(slashedTypeDescriptor, seen)) { + return true; + } + } + } + return false; + } + + List annotations = null; + + public static final List NO_ANNOTATIONS = Collections.emptyList(); + + + public List findConditionalOnMissingBeanValue() { + List findAnnotationValue = findAnnotationValue(AtConditionalOnMissingBean, false); + if (findAnnotationValue==null) { + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + if (an.desc.equals(AtConditionalOnMissingBean)) { + System.out.println("??? found nothing on this @COC annotated thing "+this.getName()); + } + } + } + } + return findAnnotationValue; + } + + public List findConditionalOnClassValue() { + List findAnnotationValue = findAnnotationValue(AtConditionalOnClass, false); + if (findAnnotationValue==null) { + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + if (an.desc.equals(AtConditionalOnClass)) { + System.out.println("??? found nothing on this @COC annotated thing "+this.getName()); + } + } + } + } + return findAnnotationValue; + } + + public List findEnableConfigurationPropertiesValue() { + List values = findAnnotationValue(AtEnableConfigurationProperties, false); + return values; + } + + public Map> findImports() { + return findAnnotationValueWithHostAnnotation(AtImports, true, new HashSet<>()); + } + + public List findAnnotationValue(String annotationType, boolean searchMeta) { + return findAnnotationValue(annotationType, searchMeta, new HashSet<>()); + } + + @SuppressWarnings("unchecked") + public Map> findAnnotationValueWithHostAnnotation(String annotationType, boolean searchMeta, Set visited) { + if (!visited.add(this.getName())) { + return Collections.emptyMap(); + } + Map> collectedResults = new LinkedHashMap<>(); + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + if (an.desc.equals(annotationType)) { + List values = an.values; + if (values != null) { + for (int i=0;i importedReferences = ((List)values.get(i+1)) + .stream() + .map(t -> t.getDescriptor()) + .collect(Collectors.toList()); + collectedResults.put(this.getName().replace("/", "."), importedReferences); + } + } + } + } + } + if (searchMeta) { + for (AnnotationNode an: node.visibleAnnotations) { + // For example @EnableSomething might have @Import on it + Type annoType = null; + try { + annoType = typeSystem.Lresolve(an.desc); + } catch (MissingTypeException mte) { + System.out.println("SBG: WARNING: Unable to find "+an.desc+" skipping..."); + continue; + } + collectedResults.putAll(annoType.findAnnotationValueWithHostAnnotation(annotationType, searchMeta, visited)); + } + } + } + return collectedResults; + } + + + @SuppressWarnings("unchecked") + public List findAnnotationValue(String annotationType, boolean searchMeta, Set visited) { + if (!visited.add(this.getName())) { + return Collections.emptyList(); + } + List collectedResults = new ArrayList<>(); + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + if (an.desc.equals(annotationType)) { + List values = an.values; + if (values != null) { + for (int i=0;i)values.get(i+1)) + .stream() + .map(t -> t.getDescriptor()) + .collect(Collectors.toCollection(() -> collectedResults)); + } + } + } + } + } + if (searchMeta) { + for (AnnotationNode an: node.visibleAnnotations) { + // For example @EnableSomething might have @Import on it + Type annoType = typeSystem.Lresolve(an.desc); + collectedResults.addAll(annoType.findAnnotationValue(annotationType, searchMeta, visited)); + } + } + } + return collectedResults; + } + + private List getAnnotations() { + if (annotations == null) { + annotations = new ArrayList<>(); + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + try { + annotations.add(this.typeSystem.Lresolve(an.desc)); + } catch (MissingTypeException mte) { + // that's ok you weren't relying on it anyway! + } + } + } +// if (node.invisibleAnnotations != null) { +// for (AnnotationNode an: node.invisibleAnnotations) { +// try { +// annotations.add(this.typeSystem.Lresolve(an.desc)); +// } catch (MissingTypeException mte) { +// // that's ok you weren't relying on it anyway! +// } +// } +// } + if (annotations.size() == 0) { + annotations = NO_ANNOTATIONS; + } + } + return annotations; + } + + public Type findAnnotation(Type searchType) { + List annos = getAnnotations(); + for (Type anno : annos) { + if (anno.equals(searchType)) { + return anno; + } + } + return null; + } + + /** + * @return true if meta annotated with org.springframework.stereotype.Indexed + */ + public Entry isIndexedOrEntity() { + Type indexedType = isMetaAnnotated2("Lorg/springframework/stereotype/Indexed;"); + if (indexedType != null) { + return new AbstractMap.SimpleImmutableEntry(this.node.name.replace("/", "."),indexedType.getName().replace("/", ".")); + } else { + indexedType = isMetaAnnotated2("Ljavax/persistence/Entity;"); + if (indexedType != null) { + return new AbstractMap.SimpleImmutableEntry(this.node.name.replace("/", "."),"javax.persistence.Entity"); + } + Type t = isIndexedInHierarchy(); + if ( t != null) { + // This should catch repositories where the Repository interface is marked @Indexed + //app.main.model.FooRepository=org.springframework.data.repository.Repository") + return new AbstractMap.SimpleImmutableEntry(this.node.name.replace("/","."), t.node.name.replace("/",".")); + } + return null; + } + } + + private Type isIndexedInHierarchy() { + if (isAnnotated("Lorg/springframework/stereotype/Indexed;")) { + return this; + } + Type[] is = getInterfaces(); + for (Type i: is) { + Type t = i.isIndexedInHierarchy(); + if (t != null) { + return t; + } + } + Type sc = getSuperclass(); + if (sc!=null) { + return sc.isIndexedInHierarchy(); + } + return null; + } +// app.main.model.FooRepository=org.springframework.data.repository.Repository +// public List findConditionalOnClassValue() { +// if (node.visibleAnnotations != null) { +// for (AnnotationNode an : node.visibleAnnotations) { +// if (an.desc.equals("Lorg/springframework/boot/autoconfigure/condition/ConditionalOnClass;")) { +// List values = an.values; +// for (int i=0;i)values.get(i+1)) +// .stream() +// .map(t -> t.getDescriptor()) +// .collect(Collectors.toList()); +// } +// } +//// for (Object o: values) { +//// System.out.println("<> "+o+" "+(o==null?"":o.ge + + private Type isMetaAnnotated2(String Ldescriptor) { + return isMetaAnnotated2(Ldescriptor, new HashSet<>()); + } + + private boolean isAnnotated(String Ldescriptor) { + if (node.visibleAnnotations != null) { + for (AnnotationNode an: node.visibleAnnotations) { + if (an.desc.equals(Ldescriptor)) { + return true; + } + } + } + return false; + } + + private Type isMetaAnnotated2(String Ldescriptor, Set seen) { + if (node.visibleAnnotations != null) { + for (AnnotationNode an: node.visibleAnnotations) { + if (seen.add(an.desc)) { + if (an.desc.equals(Ldescriptor)) { + return this;//typeSystem.Lresolve(an.desc); + } else { + Type annoType = typeSystem.Lresolve(an.desc); + Type meta = annoType.isMetaAnnotated2(Ldescriptor, seen); + if (meta != null) { + return meta; + } + } + } + } + } + return null; + } + + public List getNestedTypes() { + List result = null; + List innerClasses = node.innerClasses; + for (InnerClassNode inner: innerClasses) { + if (inner.outerName==null || !inner.outerName.equals(getName())) { +// System.out.println("SKIPPPING "+inner.name+" because outer is "+inner.outerName+" and we are looking at "+getName()); + continue; + } + if (inner.name.equals(getName())) { + continue; + } + Type t = typeSystem.resolve(inner.name); // aaa/bbb/ccc/Ddd$Eee + if (result == null) { + result = new ArrayList<>(); + } + result.add(t); + } + return result==null?Collections.emptyList():result; + } + + public String getDescriptor() { + return "L"+node.name.replace(".", "/")+";"; + } + + /** + * Find @CompilationHints directly on this type or used as a meta-annotation on annotations on this type. + */ + public Map> getHints() { + Map> hints = new LinkedHashMap<>(); + CompilationHint hint = proposedAnnotations.get(getDescriptor()); + if (hint !=null) { + List s = new ArrayList<>(); + s.add(this); + hints.put(new HintDescriptor(s, hint.skipIfTypesMissing, hint.follow, hint.name), null); + } + if (node.visibleAnnotations != null) { + for (AnnotationNode an: node.visibleAnnotations) { + Type annotationType = typeSystem.Lresolve(an.desc, true); + if (annotationType == null) { + System.out.println("Couldn't resolve "+an.desc); + } else { + Stack s = new Stack<>(); + s.push(this); + annotationType.collectHints(an, hints, new HashSet<>(), s); + } + } + } + if (implementsImportSelector() && hints.size()==0) { + throw new IllegalStateException("No @CompilationHint found for import selector: "+getDottedName()); + } + + return hints.size()==0? Collections.emptyMap():hints; + } + + // TODO repeatable annotations... + + private void collectHints(AnnotationNode an, Map> hints, Set visited, Stack annotationChain) { + if (!visited.add(an)) { + return; + } + try { + annotationChain.push(this); + // Am I a compilation hint? + CompilationHint hint = proposedAnnotations.get(an.desc); + if (hint !=null) { + hints.put(new HintDescriptor(new ArrayList<>(annotationChain), hint.skipIfTypesMissing, hint.follow, hint.name), collectTypes(an)); + } + // check for meta annotation + if (node.visibleAnnotations != null) { + for (AnnotationNode an2: node.visibleAnnotations) { + Type annotationType = typeSystem.Lresolve(an2.desc, true); + if (annotationType == null) { + System.out.println("Couldn't resolve "+an2.desc); + } else { + annotationType.collectHints(an2, hints, visited, annotationChain); + } + } + } + } finally { + annotationChain.pop(); + } + } + + private CompilationHint findCompilationHintHelper(HashSet visited) { + if (!visited.add(this)) { + return null; + } + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + CompilationHint compilationHint = proposedAnnotations.get(an.desc); + if (compilationHint != null) { + return compilationHint; + } + Type resolvedAnnotation = typeSystem.Lresolve(an.desc); + compilationHint = resolvedAnnotation.findCompilationHintHelper(visited); + if (compilationHint != null) { + return compilationHint; + } + } + } + return null; + } + + private List collectTypes(AnnotationNode an) { + List values = an.values; + if (values != null) { + for (int i=0;i importedReferences = ((List)values.get(i+1)) + .stream() + .map(t -> t.getDescriptor()) + .collect(Collectors.toList()); + return importedReferences; + } + } + } + return Collections.emptyList(); + } + + private static Map proposedAnnotations = new HashMap<>(); + + static { + // @ConditionalOnClass has @CompilationHint(skipIfTypesMissing=true, follow=false) + proposedAnnotations.put(AtConditionalOnClass, new CompilationHint(true,false)); + + // @ConditionalOnMissingBean has @CompilationHint(skipIfTypesMissing=true, follow=false) + proposedAnnotations.put(AtConditionalOnMissingBean, new CompilationHint(true, false)); + + // TODO can be {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} + // @Imports has @CompilationHint(skipIfTypesMissing=false?, follow=true) + proposedAnnotations.put(AtImports, new CompilationHint(false, true)); + + // @Conditional has @CompilationHint(skipIfTypesMissing=false, follow=false) + proposedAnnotations.put(AtConditional, new CompilationHint(false, false)); + + // TODO do configuration properties chain? + // @EnableConfigurationProperties has @CompilationHint(skipIfTypesMissing=false, follow=false) + proposedAnnotations.put(AtEnableConfigurationProperties, new CompilationHint(false, false)); + + // @EnableConfigurationPropertiesImportSelector has + // @CompilationHint(skipIfTypesMissing=false, follow=false, name={ + // ConfigurationPropertiesBeanRegistrar.class.getName(), + // ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }) + // proposedAnnotations.put(AtEnableConfigurationProperties, new CompilationHint(false, false)); + + // CacheConfigurationImportSelector has + // @CompilationHint(skipIfTypesMissing=true, follow=false, name={ + // "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"}) + proposedAnnotations.put(CacheConfigurationImportSelector, + new CompilationHint(false,false, new String[] { + "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"} + )); + + proposedAnnotations.put(RabbitConfigurationImportSelector, + new CompilationHint(true,true, new String[] { + "org.springframework.amqp.rabbit.annotation.RabbitBootstrapConfiguration"} + )); + + // TransactionManagementConfigurationSelector has + // @CompilationHint(skipIfTypesMissing=true, follow=true, name={...}) + proposedAnnotations.put(TransactionManagementConfigurationSelector, + new CompilationHint(true, true, new String[] { + "org.springframework.context.annotation.AutoProxyRegistrar", + "org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration", + "org.springframework.transaction.aspectj.AspectJJtaTransactionManagementConfiguration", + "org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration"} + )); + + // EnableSpringDataWebSupport. TODO: there are others in spring.factories. + proposedAnnotations.put(SpringDataWebConfigurationSelector, + new CompilationHint(true, true, new String[] { + "org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration", + "org.springframework.data.web.config.SpringDataWebConfiguration"} + )); + + // EnableSpringDataWebSupport. TODO: there are others in spring.factories. + proposedAnnotations.put(SpringDataWebQueryDslSelector, + new CompilationHint(true, true, new String[] { + "org.springframework.data.web.config.QuerydslWebConfiguration"} + )); + + // EnableConfigurationPropertiesImportSelector has + // @CompilationHint(skipIfTypesMissing=true, follow=false, name={ + // "org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector$ConfigurationPropertiesBeanRegistrar", + // "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar"}) + proposedAnnotations.put(EnableConfigurationPropertiesImportSelector, + new CompilationHint(false,false, new String[] { + "org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector$ConfigurationPropertiesBeanRegistrar", + "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar"} + )); + + + // Not quite right... this is a superclass of a selector we've already added... + proposedAnnotations.put(AdviceModeImportSelector, + new CompilationHint(true, true, new String[0] + )); + + + // Spring Security! + proposedAnnotations.put(SpringWebMvcImportSelector, + new CompilationHint(false, true, new String[] { + "org.springframework.web.servlet.DispatcherServlet", + "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" + })); + proposedAnnotations.put(OAuth2ImportSelector, + new CompilationHint(false, true, new String[] { + "org.springframework.security.oauth2.client.registration.ClientRegistration", + "org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration" + })); + + proposedAnnotations.put(HypermediaConfigurationImportSelector, + new CompilationHint(false, true, new String[] { + "org.springframework.hateoas.config.HypermediaConfigurationImportSelector" + })); + + proposedAnnotations.put(WebStackImportSelector, + new CompilationHint(false, true, new String[] { + "org.springframework.hateoas.config.WebStackImportSelector" + })); + } + + private boolean implementsImportSelector() { + return implementsInterface(fromLdescriptorToSlashed(ImportSelector)); + } + + private String fromLdescriptorToSlashed(String Ldescriptor) { + return Ldescriptor.substring(1,Ldescriptor.length()-1); + } + + private CompilationHint findCompilationHint(Type annotationType) { + String descriptor = "L"+annotationType.getName().replace(".", "/")+";"; + CompilationHint hint = proposedAnnotations.get(descriptor); + if (hint !=null) { + return hint; + } else { + // check for meta annotation + return annotationType.findCompilationHintHelper(new HashSet<>()); + } + } + + // TODO what about AliasFor usage in spring annotations themselves? does that trip this code up when we start looking at particular fields? + + static class CompilationHint { + boolean follow; + boolean skipIfTypesMissing; + private String[] name; + + public CompilationHint(boolean skipIfTypesMissing, boolean follow) { + this(skipIfTypesMissing,follow,new String[] {}); + } + + // TODO what about whether you need to reflect on ctors/methods/fields? + public CompilationHint(boolean skipIfTypesMissing, boolean follow, String[] name) { + this.skipIfTypesMissing = skipIfTypesMissing; + this.follow = follow; + this.name = name; + } + } + + public void collectMissingAnnotationTypesHelper(Set missingAnnotationTypes, HashSet visited) { + if (!visited.add(this)) { + return; + } + if (node.visibleAnnotations != null) { + for (AnnotationNode an: node.visibleAnnotations) { + Type annotationType = typeSystem.Lresolve(an.desc, true); + if (annotationType == null) { + missingAnnotationTypes.add(an.desc.substring(0,an.desc.length()-1).replace("/", ".")); + } else { + annotationType.collectMissingAnnotationTypesHelper(missingAnnotationTypes, visited); + } + } + } + } + +// @SuppressWarnings("unchecked") +// public void findCompilationHints(String annotationType, Map> hintCollector, Set visited) { +// if (!visited.add(this.getName())) { +// return Collections.emptyMap(); +// } +// Map> collectedResults = new LinkedHashMap<>(); +// if (node.visibleAnnotations != null) { +// for (AnnotationNode an : node.visibleAnnotations) { +// if (an.desc.equals(annotationType)) { +// List values = an.values; +// if (values != null) { +// for (int i=0;i importedReferences = ((List)values.get(i+1)) +// .stream() +// .map(t -> t.getDescriptor()) +// .collect(Collectors.toList()); +// collectedResults.put(this.getName().replace("/", "."), importedReferences); +// } +// } +// } +// } +// } +// if (searchMeta) { +// for (AnnotationNode an: node.visibleAnnotations) { +// // For example @EnableSomething might have @Import on it +// Type annoType = null; +// try { +// annoType = typeSystem.Lresolve(an.desc); +// } catch (MissingTypeException mte) { +// System.out.println("SBG: WARNING: Unable to find "+an.desc+" skipping..."); +// continue; +// } +// collectedResults.putAll(annoType.findCompilationHints(annotationType, visited)); +// } +// } +// } +// return collectedResults; +// } + + + // Assume @ConditionalOnClass has @CompilationHint(skipIfTypesMissing=true) and both value() and name() in + // the annotation would have @CompilationTypeList + + + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/TypeSystem.java b/src/main/java/org/springframework/graal/type/TypeSystem.java new file mode 100644 index 000000000..a4a042672 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/TypeSystem.java @@ -0,0 +1,539 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graal.type; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; + +/** + * Simple type system with some rudimentary caching. + */ +public class TypeSystem { + + public static String SPRING_AT_CONFIGURATION = "Lorg/springframework/context/annotation/Configuration;"; + + // Map of all types on the classpath that have some kind of annotations on them + Map annotatedTypes; + + // Classpath from which this type system will resolve types + private List classpath; + + // Cache of resolved types TODO time out entries? + private Map typeCache = new HashMap<>(); + + // Map of which zip files contain which packages TODO split package support + private Map packageCache = new HashMap<>(); + + // Map of which application files contain particular packages + private Map> appPackages = new HashMap<>(); + + + public static TypeSystem get(List classpath) { + return new TypeSystem(classpath); + } + + public TypeSystem(List classpath) { + this.classpath = classpath; + index(); + } + + public List getClasspath() { + return classpath; + } + + public Type resolveDotted(String dottedTypeName) { + String slashedTypeName = toSlashedName(dottedTypeName); + return resolveSlashed(slashedTypeName); + } + + public boolean canResolveSlashed(String slashedTypeName) { + try { + return resolveSlashed(slashedTypeName) != null; + } catch (RuntimeException re) { + if (re.getMessage().startsWith("Unable to find class file for")) { + return false; + } + throw re; + } + } + + public Type resolveSlashed(String slashedTypeName) { + return resolveSlashed(slashedTypeName, false); + } + + public Type resolveSlashed(String slashedTypeName, boolean allowNotFound) { + Type type = typeCache.get(slashedTypeName); + if (type == Type.MISSING) { + if (allowNotFound) { + return null; + } else { + throw new MissingTypeException(slashedTypeName); + } + } + if (type != null) { + return type; + } + byte[] bytes = find(slashedTypeName); + if (bytes == null) { + // System class? + InputStream resourceAsStream = Thread.currentThread().getContextClassLoader() + .getResourceAsStream(slashedTypeName + ".class"); + if (resourceAsStream == null) { + // cache a missingtype so we don't go looking again! + typeCache.put(slashedTypeName, Type.MISSING); + if (allowNotFound) { + return null; + } else { + throw new MissingTypeException(slashedTypeName); + } + } + try { + bytes = loadFromStream(resourceAsStream); + } catch (RuntimeException e) { + throw new RuntimeException("Problems loading class from resource stream: " + slashedTypeName, e); + } + } + ClassNode node = new ClassNode(); + ClassReader reader = new ClassReader(bytes); + reader.accept(node, ClassReader.SKIP_DEBUG); + type = Type.forClassNode(this, node); + typeCache.put(slashedTypeName, type); + return type; + } + + private String toSlashedName(String dottedTypeName) { + return dottedTypeName.replace(".", "/"); + } + + public boolean canResolve(String classname) { + if (classname.contains(".")) { + throw new RuntimeException("Dont pass dotted names to resolve() :" + classname); + } + return canResolveSlashed(classname); + } + + public Type resolve(String classname, boolean allowNotFound) { + if (classname.contains(".")) { + throw new RuntimeException("Dont pass dotted names to resolve() :" + classname); + } + return resolveSlashed(classname, allowNotFound); + } + + public Type resolve(String classname) { + return resolve(classname, false); + } + + public Type Lresolve(String desc) { + return resolve(desc.substring(1, desc.length() - 1)); + } + + public Type Lresolve(String desc, boolean silent) { + try { + return resolve(desc.substring(1, desc.length() - 1)); + } catch (MissingTypeException mte) { + if (silent) + return null; + else + throw mte; + } + } + + public Set resolveCompleteFindMissingTypes(Type type) { + return resolveComplete(type.getDescriptor()); + } + + public Set resolveCompleteFindMissingAnnotationTypes(Type type) { + Set missingAnnotationTypes = new LinkedHashSet<>(); + type.collectMissingAnnotationTypesHelper(missingAnnotationTypes, new HashSet<>()); + return missingAnnotationTypes; + } + + /** + * Verifies the type plus all its super types, interfaces and any type references in generic specifications exist. + * @return List of missing types, empty if all good! + */ + public Set resolveComplete(String desc) { + Set missingTypes = new LinkedHashSet<>(); + resolveComplete(desc.substring(1, desc.length()-1), missingTypes, new HashSet<>()); + return missingTypes; + } + + private void resolveComplete(String slashedDescriptor, Set missingTypes, Set visited) { + if (visited.add(slashedDescriptor)) { + Type baseType = resolve(slashedDescriptor, true); + if (baseType == null) { + missingTypes.add(slashedDescriptor); + } else { + // Check generics + List typesInSignature = baseType.getTypesInSignature(); + for (String t: typesInSignature) { + System.out.println("Found this "+t+" in signature of "+baseType.getName()); + } + String superclassString = baseType.getSuperclassString(); + if (superclassString != null) { + resolveComplete(superclassString, missingTypes, visited); + } + List interfaces = baseType.getInterfacesStrings(); + if (interfaces != null) { + for (String interfce: interfaces) { + resolveComplete(interfce, missingTypes, visited); + } + } + } + } + } + + public void index() { + for (String s : classpath) { + File f = new File(s); + if (f.isDirectory()) { + indexDir(f); + } else { + indexJar(f); + } + } + } + + public void indexDir(File dir) { + Path root = Paths.get(dir.toURI()); + try { + Files.walk(root).filter(f -> f.toString().endsWith(".class")).map(f -> { + String name = f.toString().substring(root.toString().length() + 1); + int lastSlash = name.lastIndexOf("/"); + if (lastSlash != -1 && name.endsWith(".class")) { + return name.substring(0, lastSlash); + } + return null; + }).forEach(n -> { + if (n != null) { + List dirs = appPackages.get(n); + if (dirs == null) { + dirs = new ArrayList<>(); + appPackages.put(n, dirs); + } + dirs.add(dir); + } + }); + } catch (IOException ioe) { + throw new IllegalStateException("Unable to walk " + dir, ioe); + } + } + + public void indexJar(File jar) { + // Walk the jar, index entries and cache package > this jar + try { + try (ZipFile zf = new ZipFile(jar)) { + Enumeration entries = zf.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.endsWith(".class")) { + int lastSlash = name.lastIndexOf("/"); + if (lastSlash != -1 && name.endsWith(".class")) { + packageCache.put(name.substring(0, lastSlash), jar); + } + } + } + } + } catch (IOException ioe) { + throw new RuntimeException("Problem during scan of " + jar, ioe); + } + } + + public byte[] find(String slashedTypeName) { + String search = slashedTypeName + ".class"; + try { + int index = slashedTypeName.lastIndexOf("/"); + String packageName = index==-1?"":slashedTypeName.substring(0, index); + + if (appPackages.containsKey(packageName)) { + List list = appPackages.get(packageName); + for (File f : list) { + File toTry = new File(f, search); + if (toTry.exists()) { + return loadFromStream(new FileInputStream(toTry)); + } + } + } else { + File jarfile = packageCache.get(packageName); + if (jarfile != null) { + try (ZipFile zf = new ZipFile(jarfile)) { + Enumeration entries = zf.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.equals(search)) { + return loadFromStream(zf.getInputStream(entry)); + } + } + } + } + } + + return null; + } catch (IOException ioe) { + throw new RuntimeException("Problem finding " + slashedTypeName, ioe); + } + } + + public static byte[] loadFromStream(InputStream stream) { + try { + BufferedInputStream bis = new BufferedInputStream(stream); + int size = 2048; + byte[] theData = new byte[size]; + int dataReadSoFar = 0; + byte[] buffer = new byte[size / 2]; + int read = 0; + while ((read = bis.read(buffer)) != -1) { + if ((read + dataReadSoFar) > theData.length) { + // need to make more room + byte[] newTheData = new byte[theData.length * 2]; + // System.out.println("doubled to " + newTheData.length); + System.arraycopy(theData, 0, newTheData, 0, dataReadSoFar); + theData = newTheData; + } + System.arraycopy(buffer, 0, theData, dataReadSoFar, read); + dataReadSoFar += read; + } + bis.close(); + // Resize to actual data read + byte[] returnData = new byte[dataReadSoFar]; + System.arraycopy(theData, 0, returnData, 0, dataReadSoFar); + return returnData; + } catch (IOException e) { + throw new RuntimeException("Unexpectedly unable to load bytedata from input stream", e); + } finally { + try { + stream.close(); + } catch (IOException ioe) { + } + } + } + + public String toString() { + return "TypeSystem for cp(" + classpath + ") jarPackages=#" + packageCache.size() + " appPackages=" + + appPackages; + } + + public void scan() { + // Scan the classpath for things of interest, do this only once! + for (String classpathEntry : classpath) { + File f = new File(classpathEntry); + if (f.isDirectory()) { + scanFiles(f, f); + } else { + scanArchive(f); + } + } + } + + private void scanArchive(File f) { + try (ZipFile zf = new ZipFile(f)) { + Enumeration entries = zf.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.getName().endsWith(".class")) { + ClassReader reader = new ClassReader(zf.getInputStream(entry)); + ClassNode node = new ClassNode(); + reader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + AnnotationInfo ai = new AnnotationInfo(this, node); + if (ai.hasData()) { + System.out.println("From " + entry.toString() + " got " + ai.toAnnotationString()); + annotatedTypes.put(node.name, ai); + } + } + // TODO resources? + } + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } + } + + private void scanFiles(File file, File base) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + for (File f : files) { + scanFiles(f, base); + } + } else if (file.getName().endsWith(".class")) { + try { + byte[] bytes = Files.readAllBytes(Paths.get(file.toURI())); + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + AnnotationInfo ai = new AnnotationInfo(this, node); + if (ai.hasData()) { + System.out.println("From " + file.getName() + " got " + ai.toAnnotationString()); + annotatedTypes.put(node.name, ai); + } + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } + } +// else // resource? + } + + public static class AnnotationInfo { + + private String name; + private TypeSystem typeSystem; + private List annotations; + + // if this is the annotationinfo for an annotation, this will cache meta + // annotations + private List metaAnnotationsList = null; + + // need file? + + public AnnotationInfo(TypeSystem typeSystem, ClassNode node) { + this.typeSystem = typeSystem; + this.name = node.name; + annotations = node.visibleAnnotations; + } + + public boolean hasData() { + return annotations != null && annotations.size() != 0; + } + + public String toAnnotationString() { + StringBuilder sb = new StringBuilder(); + if (annotations != null) { + for (AnnotationNode an : annotations) { + sb.append(an.desc); + sb.append("("); + List values = an.values; + if (values != null) { + for (int j = 0; j < values.size(); j += 2) { + sb.append(values.get(j)); + sb.append("="); + sb.append(values.get(j + 1)); + } + } + sb.append(")"); + } + } + return sb.toString(); + } + + public boolean hasDescriptor(String annotationDescriptor) { + for (AnnotationNode an : annotations) { + if (an.desc.equals(annotationDescriptor)) { + return true; + } + } + return false; + } + + // TODO filter out java/lang/annotation annotations? Surely we don't need all of + // them + + List getMetaAnnotations() { + if (metaAnnotationsList == null) { + metaAnnotationsList = new ArrayList<>(); + collectMetaAnnotations(); + if (metaAnnotationsList.size() == 0) { + metaAnnotationsList = Collections.emptyList(); + } + } + return metaAnnotationsList; + } + + public boolean hasDescriptorMeta(String annotationDescriptor) { + System.out.println("Checking " + name + " for " + annotationDescriptor); + for (AnnotationNode an : annotations) { + if (an.desc.equals(annotationDescriptor)) { + return true; + } + } + for (AnnotationNode an : getMetaAnnotations()) { + if (an.desc.equals(annotationDescriptor)) { + return true; + } + } + return false; + } + + private void collectMetaAnnotations() { + for (AnnotationNode an : annotations) { + // Go through our annotations and grab their meta annotations + AnnotationInfo ai = typeSystem.annotatedTypes.get(an.desc.substring(1, an.desc.length() - 1)); + if (ai != null && ai.hasData()) { + metaAnnotationsList.addAll(ai.getAnnotations()); + metaAnnotationsList.addAll(ai.getMetaAnnotations()); + if (name.endsWith("DemoApplication")) { + System.out.println("111"); + for (AnnotationNode ann : metaAnnotationsList) { + System.out.println(ann.desc); + } + System.out.println("222"); + } + } + } + } + + private Collection getAnnotations() { + return annotations; + } + } + + private void ensureScanned() { + if (annotatedTypes == null) { + annotatedTypes = new HashMap<>(); + long t = System.currentTimeMillis(); + scan(); + System.out.println("SBG: scan time: " + (System.currentTimeMillis() - t) + "ms"); + } + } + + public List findTypesAnnotated(String annotationDescriptor, boolean metaAnnotated) { + ensureScanned(); + if (metaAnnotated) { + return annotatedTypes.values().stream().filter(ai -> ai.hasDescriptorMeta(annotationDescriptor)) + .map(ai -> ai.name).collect(Collectors.toList()); + } else { + return annotatedTypes.values().stream().filter(ai -> ai.hasDescriptor(annotationDescriptor)) + .map(ai -> ai.name).collect(Collectors.toList()); + } + } + + public List findTypesAnnotationAtConfiguration(boolean metaAnnotated) { + return findTypesAnnotated(SPRING_AT_CONFIGURATION,metaAnnotated); + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/internal/svm/MessageInterpolatorIsAround.java b/src/main/java/org/springframework/internal/svm/MessageInterpolatorIsAround.java new file mode 100644 index 000000000..d1290c9b8 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/MessageInterpolatorIsAround.java @@ -0,0 +1,18 @@ +package org.springframework.internal.svm; + +import java.util.function.BooleanSupplier; + +public class MessageInterpolatorIsAround implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + try { + Class.forName("javax.validation.MessageInterpolator"); + Class.forName("org.springframework.boot.validation.MessageInterpolatorFactory"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + +} diff --git a/src/main/java/org/springframework/internal/svm/OnlyPresent.java b/src/main/java/org/springframework/internal/svm/OnlyPresent.java new file mode 100644 index 000000000..431325d22 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/OnlyPresent.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.internal.svm; + +import java.util.function.Predicate; + +class OnlyPresent implements Predicate { + + @Override + public boolean test(String type) { + try { + return Class.forName(type, false, getClass().getClassLoader()) != null; + } + catch (ClassNotFoundException ex) { + return false; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/internal/svm/Target_DefaultMethodInvokingMethodInterceptor.java b/src/main/java/org/springframework/internal/svm/Target_DefaultMethodInvokingMethodInterceptor.java new file mode 100644 index 000000000..40a96912c --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_DefaultMethodInvokingMethodInterceptor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.internal.svm; + +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.ProxyMethodInvocation; + +/** + * @author Andy Clement + */ +@TargetClass(className="org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor", onlyWith = OnlyPresent.class) +public final class Target_DefaultMethodInvokingMethodInterceptor { + + @Substitute + public Object invoke(MethodInvocation invocation) throws Throwable { + Method method = invocation.getMethod(); + if (!method.isDefault()) { + return invocation.proceed(); + } + Object[] arguments = invocation.getArguments(); + Object proxy = ((ProxyMethodInvocation)invocation).getProxy(); + try { + return method.invoke(proxy,arguments); + } catch (UndeclaredThrowableException ute) { + System.out.println("UNDECLARED THROWABLE: "+ute.getUndeclaredThrowable()); + throw ute; + } + } + +} diff --git a/src/main/java/org/springframework/internal/svm/Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor.java b/src/main/java/org/springframework/internal/svm/Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor.java new file mode 100644 index 000000000..aaca794d3 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.internal.svm; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Workaround for + * Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Unsupported method java.lang.ClassLoader.registerAsParallelCapable() is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class + at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:102) + at java.lang.ClassLoader.registerAsParallelCapable(Target_java_lang_ClassLoader.java:1204) + at org.springframework.core.DecoratingClassLoader.(DecoratingClassLoader.java:38) + at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:347) + at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:267) + at java.lang.Class.ensureInitialized(DynamicHub.java:437) + at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:232) + at java.lang.Class.ensureInitialized(DynamicHub.java:437) + at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:232) + at java.lang.Class.ensureInitialized(DynamicHub.java:437) + at org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo.getNewTempClassLoader(SpringPersistenceUnitInfo.java:93) + at org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor.getTempClassLoader(PersistenceUnitInfoDescriptor.java:78) + at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.populate(EntityManagerFactoryBuilderImpl.java:833) + at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.(EntityManagerFactoryBuilderImpl.java:219) + at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.(EntityManagerFactoryBuilderImpl.java:167) + at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:51) + at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) + at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) + + * @author Andy Clement + */ +@TargetClass(className="org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor", onlyWith = OnlyPresent.class) +public final class Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor { + + @Substitute + public ClassLoader getTempClassLoader() { + return null; + } +} diff --git a/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_SpringBootVersion.java b/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_SpringBootVersion.java new file mode 100644 index 000000000..fbde1ba68 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_SpringBootVersion.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.internal.svm; + +import org.springframework.beans.BeansException; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * + * @author Andy Clement + */ +@TargetClass(className="org.springframework.boot.SpringBootVersion", onlyWith = OnlyPresent.class) +public final class Target_org_springframework_boot_SpringBootVersion { + + @Substitute + public static String getVersion() throws BeansException { + return null; + // Without this, line 66 in SpringBootVersion 2.2.0 snapshots (>m2) fails with NPE: + + // URL codeSourceLocation = SpringBootVersion.class.getProtectionDomain() + // .getCodeSource().getLocation(); // this is line 66 + + //java.lang.NullPointerException + // at org.springframework.boot.SpringBootVersion.determineSpringBootVersion(SpringBootVersion.java:66) + // at org.springframework.boot.SpringBootVersion.getVersion(SpringBootVersion.java:56) + // at org.springframework.boot.SpringBootBanner.printBanner(SpringBootBanner.java:51) + // at org.springframework.boot.SpringApplicationBannerPrinter.print(SpringApplicationBannerPrinter.java:71) + // at org.springframework.boot.SpringApplication.printBanner(SpringApplication.java:582) + // at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) + // at com.example.func.BuncApplication.run(BuncApplication.java:55) + // at com.example.func.BuncApplication.main(BuncApplication.java:34) + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_validation_MessageInterpolatorFactory.java b/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_validation_MessageInterpolatorFactory.java new file mode 100644 index 000000000..9cd39b20c --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_validation_MessageInterpolatorFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.internal.svm; + +import javax.validation.MessageInterpolator; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.util.ClassUtils; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Substitution for MessageInterpolatorFactory. The code pattern in there misbehaves under graal so let's just fallback straight away... needs review + * + * @author Andy Clement + */ +@TargetClass(className="org.springframework.boot.validation.MessageInterpolatorFactory",onlyWith=MessageInterpolatorIsAround.class) +public final class Target_org_springframework_boot_validation_MessageInterpolatorFactory { + + @Substitute + public MessageInterpolator getObject() throws BeansException { + Class interpolatorClass = ClassUtils.resolveClassName("org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator", null); + Object interpolator = BeanUtils.instantiateClass(interpolatorClass); + return (MessageInterpolator) interpolator; + } +} diff --git a/src/main/java/org/springframework/internal/svm/Target_org_springframework_jdbc_EmbeddedDatabaseConnection.java b/src/main/java/org/springframework/internal/svm/Target_org_springframework_jdbc_EmbeddedDatabaseConnection.java new file mode 100644 index 000000000..1698fad2a --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_springframework_jdbc_EmbeddedDatabaseConnection.java @@ -0,0 +1,37 @@ +///* +// * Copyright 2019 Contributors +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * https://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +//package org.springframework.internal.svm; +// +//import org.springframework.beans.BeansException; +//import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +// +//import com.oracle.svm.core.annotate.Substitute; +//import com.oracle.svm.core.annotate.TargetClass; +// +///** +// * Substitution for EmbeddedDatabaseConnection. +// * +// * @author Andy Clement +// */ +//@TargetClass(org.springframework.boot.jdbc.EmbeddedDatabaseConnection.class) +//public final class Target_org_springframework_jdbc_EmbeddedDatabaseConnection { +// +// // working around Graal #1196 +// @Substitute +// public EmbeddedDatabaseConnection[] values() throws BeansException { +// return new EmbeddedDatabaseConnection[0]; +// } +//} \ No newline at end of file diff --git a/src/main/java/org/springframework/internal/svm/Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager.java b/src/main/java/org/springframework/internal/svm/Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager.java new file mode 100644 index 000000000..66fd90e17 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.internal.svm; + +import java.net.URL; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Workaround for + * + * Caused by: javax.persistence.PersistenceException: Unable to resolve persistence unit + * root URL at + * org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.determineDefaultPersistenceUnitRootUrl(DefaultPersistenceUnitManager.java:640) + * at + * org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.preparePersistenceUnitInfos(DefaultPersistenceUnitManager.java:462) + * at + * org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.afterPropertiesSet(DefaultPersistenceUnitManager.java:443) + * at + * org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:328) + * at + * org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1841) + * at + * org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778) + * ... 16 more Caused by: java.io.FileNotFoundException: class path resource [] cannot be + * resolved to URL because it does not exist at + * org.springframework.core.io.ClassPathResource.getURL(ClassPathResource.java:195) at + * org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.determineDefaultPersistenceUnitRootUrl(DefaultPersistenceUnitManager.java:636) + * @author Andy Clement + */ +@TargetClass(className = "org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager", onlyWith = OnlyPresent.class) +public final class Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager { + + @Substitute + public URL determineDefaultPersistenceUnitRootUrl() { + return null; + } +} diff --git a/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java b/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java new file mode 100644 index 000000000..2c5bed6e3 --- /dev/null +++ b/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.core; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.springframework.dao.DataAccessException; +import org.springframework.lang.Nullable; + +/** + * Generic callback interface for code that operates on a JDBC Connection. + * Allows to execute any number of operations on a single Connection, + * using any type and number of Statements. + * + *

This is particularly useful for delegating to existing data access code + * that expects a Connection to work on and throws SQLException. For newly + * written code, it is strongly recommended to use JdbcTemplate's more specific + * operations, for example a {@code query} or {@code update} variant. + * + * @author Juergen Hoeller + * @since 1.1.3 + * @param the result type + * @see JdbcTemplate#execute(ConnectionCallback) + * @see JdbcTemplate#query + * @see JdbcTemplate#update + */ +@FunctionalInterface +public interface ConnectionCallback { + + /** + * Gets called by {@code JdbcTemplate.execute} with an active JDBC + * Connection. Does not need to care about activating or closing the + * Connection, or handling transactions. + *

If called without a thread-bound JDBC transaction (initiated by + * DataSourceTransactionManager), the code will simply get executed on the + * JDBC connection with its transactional semantics. If JdbcTemplate is + * configured to use a JTA-aware DataSource, the JDBC Connection and thus + * the callback code will be transactional if a JTA transaction is active. + *

Allows for returning a result object created within the callback, i.e. + * a domain object or a collection of domain objects. Note that there's special + * support for single step actions: see {@code JdbcTemplate.queryForObject} + * etc. A thrown RuntimeException is treated as application exception: + * it gets propagated to the caller of the template. + * @param con active JDBC Connection + * @return a result object, or {@code null} if none + * @throws SQLException if thrown by a JDBC method, to be auto-converted + * to a DataAccessException by a SQLExceptionTranslator + * @throws DataAccessException in case of custom exceptions + * @see JdbcTemplate#queryForObject(String, Class) + * @see JdbcTemplate#queryForRowSet(String) + */ + @Nullable + T doInConnection(Connection con) throws SQLException, DataAccessException; + +} diff --git a/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java b/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java new file mode 100644 index 000000000..3b0239d24 --- /dev/null +++ b/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.jdbc.datasource.embedded; + +/** + * A supported embedded database type. + * + * @author Keith Donald + * @author Oliver Gierke + * @since 3.0 + */ +public enum EmbeddedDatabaseType { + + /** The Hypersonic Embedded Java SQL Database. */ + HSQL, + + /** The H2 Embedded Java SQL Database Engine. */ + H2, + + /** The Apache Derby Embedded SQL Database. */ + DERBY + +} diff --git a/src/main/resources/initialization.json b/src/main/resources/initialization.json new file mode 100644 index 000000000..72823ce4d --- /dev/null +++ b/src/main/resources/initialization.json @@ -0,0 +1,179 @@ +{ +"runtimeInitialization": +[ +{"package": "io.netty.handler.codec.http2"}, +{"class": "reactor.netty.tcp.TcpClientSecure"}, +{"class": "reactor.netty.http.client.HttpClientSecure"}, +{"class": "sun.reflect.misc.Trampoline"}, +{"class": "reactor.netty.http.server.HttpServer"}, +{"class": "ch.qos.logback.classic.spi.PackagingDataCalculator"} +], +"buildTimeInitialization": +[ + // vv vanilla-grpc + {"class": "io.netty.handler.codec.http2.CharSequenceMap"}, + {"class": "io.netty.handler.codec.http2.Http2Headers$PseudoHeaderName"}, + // ^^ vanilla-grpc + // These whilst working on the tx sample upgrading to graal 19.2 and boot 2.2.0.m5 + //{"package": "org.springframework.aop"}, + {"class": "org.springframework.aop.TargetSource"}, + {"class":"org.springframework.aop.framework.Advised"}, + {"class": "org.springframework.aop.Advisor"}, + {"class": "org.springframework.aop.Advisor$1"}, + {"class": "org.aopalliance.aop.Advice"}, + {"class": "org.springframework.boot.CommandLineRunner"}, + {"class": "app.main.Finder"}, + {"class": "org.springframework.core.DecoratingProxy"}, + {"class": "org.springframework.jdbc.datasource.ConnectionProxy"}, + // + {"class": "org.slf4j.helpers.NOPLogger"}, + {"class": "io.netty.handler.codec.http2.ReadOnlyHttp2Headers"}, +{"class": "org.springframework.boot.validation.MessageInterpolatorFactory"}, +{"class": "com.google.protobuf.Extension"}, +{"class": "com.google.protobuf.ExtensionLite"}, +{"class": "com.google.protobuf.ExtensionRegistry"}, +{"class": "org.springframework.transaction.annotation.Isolation"}, +{"class": "org.springframework.transaction.annotation.Propagation"}, +{"class": "org.springframework.http.HttpStatus"}, +{"class": "org.h2.Driver"}, +{"package": "reactor.netty.resources"}, +{"package": "reactor.netty"}, +{"package": "reactor.netty.channel"}, +{"package": "reactor.netty.http"}, +{"package": "reactor.netty.http.client"}, +{"package": "reactor.netty.http.server"}, +{"package": "reactor.netty.http.websocket"}, +{"package": "reactor.netty.resources"}, +{"package": "reactor.netty.tcp"}, +{"package": "reactor.netty.udp"}, +{"class": "reactor.netty.resources.ConnectionProvider"}, +{"package": "reactor.util"}, +{"class": "reactor.util.Loggers$Slf4JLogger"}, +{"package": "org.apache.logging.log4j"}, +{"class": "org.jboss.logging.LoggerProviders"}, +{"class": "org.jboss.logging.Log4j2LoggerProvider"}, +{"class": "org.hibernate.validator.internal.util.logging.Log_$logger"}, +{"class": "org.jboss.logging.Log4j2Logger"}, +{"package": "org.apache.logging.slf4j"}, +{"package": "org.jboss.logging"}, +{"class": "org.springframework.util.unit.DataSize"}, +{"class": "org.hibernate.validator.internal.engine.ConfigurationImpl"}, +{"class": "org.springframework.core.annotation.AnnotationFilter"}, +{"class": "org.springframework.core.annotation.AnnotationFilter$1"}, +{"class": "org.springframework.core.annotation.PackagesAnnotationFilter"}, +{"class": "org.springframework.util.StringUtils"}, +{"class": "org.springframework.util.Assert"}, +{"class": "reactor.netty.ConnectionObserver"}, +{"class": "org.slf4j.helpers.Util"}, +{"class": "org.slf4j.helpers.NOPLoggerFactory"}, +{"class": "org.slf4j.helpers.SubstituteLoggerFactory"}, +{"class": "org.slf4j.LoggerFactory"}, +{"class": "org.slf4j.impl.StaticLoggerBinder"}, +{"class": "ch.qos.logback.classic.util.ContextSelectorStaticBinder"}, +{"class": "ch.qos.logback.classic.LoggerContext"}, +{"class": "ch.qos.logback.classic.selector.DefaultContextSelector"}, +{"class": "ch.qos.logback.classic.Logger"}, +{"class": "ch.qos.logback.classic.selector.DefaultContextSelector"}, +{"class": "ch.qos.logback.classic.spi.LoggerContextVO"}, +{"class": "ch.qos.logback.classic.spi.TurboFilterList"}, +{"class": "ch.qos.logback.core.BasicStatusManager"}, +{"class": "ch.qos.logback.core.spi.LogbackLock"}, +{"class": "ch.qos.logback.core.status.InfoStatus"}, +{"class": "ch.qos.logback.core.util.COWArrayList"}, +{"class": "org.hibernate.validator.internal.metadata.provider.ProgrammaticMetaDataProvider"}, +{"package": "org.hibernate.validator.internal.metadata.provider"}, +{"class": "org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder"}, +{"package": "org.hibernate.validator.internal.metadata.aggregated"}, +{"class": "org.hibernate.validator.internal.metadata.raw.ConstrainedElement$ConstrainedElementKind"}, +{"package": "org.hibernate.validator.internal.metadata.raw"}, +{"class": "org.hibernate.validator.internal.engine.ValidatorImpl"}, +{"package": "org.hibernate.validator.internal.engine"}, +{"class": "ch.qos.logback.classic.layout.TTLLLayout"}, +{"class": "ch.qos.logback.core.util.CachingDateFormatter"}, +{"class": "ch.qos.logback.classic.pattern.ThrowableProxyConverter"}, +{"class": "org.slf4j.helpers.FormattingTuple"}, +{"class": "ch.qos.logback.classic.util.LoggerNameUtil"}, +{"class": "ch.qos.logback.core.spi.FilterAttachableImpl"}, +{"class": "org.slf4j.helpers.MessageFormatter"}, +{"package": "ch.qos.logback.classic.spi"}, +{"class": "org.hibernate.validator.internal.util.Contracts"}, +{"class": "org.hibernate.validator.internal.engine.resolver.TraversableResolver"}, +{"class": "org.hibernate.validator.internal.util.privilegedactions.LoadClass"}, +{"class": "org.hibernate.validator.internal.engine.resolver.TraversableResolvers"}, +{"class": "org.hibernate.validator.internal.util.logging.Log"}, +{"class": "org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator"}, +{"class": "org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator"}, +{"class": "org.hibernate.validator.internal.util.CollectionHelper"}, +{"class": "org.hibernate.validator.internal.xml.config.ResourceLoaderHelper"}, +{"class": "org.hibernate.validator.internal.engine.ValidatorFactoryImpl"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager"}, +{"class": "javax.validation.Validation"}, +{"class": "javax.validation.ConstraintValidator"}, +{"package": "javax.validation"}, +{"class": "org.hibernate.validator.constraints.CompositionType"}, +{"package": "org.hibernate.validator.constraints"}, +{"class": "org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator"}, +{"package": "org.hibernate.validator.internal.engine.groups"}, +{"class": "javax.validation.ValidationException"}, +{"package": "org.hibernate.validator.internal.engine.valueextraction"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.IntArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.LongArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.FloatArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ShortArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.DoubleArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.CharArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ListValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.BooleanArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ObjectArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.MapValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.MapKeyExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.IterableValueExtractor"}, +{"class": "org.hibernate.validator.internal.util.TypeHelper"}, +{"class": "org.hibernate.validator.internal.util.ReflectionHelper"}, +{"class": "org.hibernate.validator.internal.util.ExecutableHelper"}, +{"package": "org.hibernate.validator.internal.util"}, +{"class": "org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping"}, +{"package": "org.hibernate.validator.internal.cfg.context"}, +{"class": "org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl"}, +{"package": "org.hibernate.validator.internal.metadata.core"}, +{"class": "org.hibernate.validator.internal.metadata.core.ConstraintHelper"}, +{"package": "org.hibernate.validator.internal.metadata.aggregated.rule"}, +{"package": "org.hibernate.validator.internal.engine.constraintvalidation"}, +{"class": "org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager"}, +{"class": "org.hibernate.validator.internal.metadata.aggregated.rule.OverridingMethodMustNotAlterParameterConstraints"}, +{"class": "org.hibernate.validator.internal.engine.constraintvalidation.ClassBasedValidatorDescriptor"}, +{"class": "org.hibernate.validator.internal.engine.scripting.DefaultScriptEvaluatorFactory"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ByteArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.OptionalValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.OptionalIntValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.OptionalDoubleValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.OptionalLongValueExtractor"}, +//{"class": "org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator"}, +{"class": "org.hibernate.validator.internal.util.logging.Messages"}, +{"class": "org.hibernate.validator.internal.util.logging.Messages_$bundle"}, +{"class": "org.hibernate.validator.internal.xml.config.ValidationXmlParser"}, +{"class": "org.hibernate.validator.resourceloading.PlatformResourceBundleLocator$AggregateResourceBundle"}, +{"class": "org.hibernate.validator.resourceloading.PlatformResourceBundleLocator$AggregateResourceBundleControl"}, +{"class": "org.hibernate.validator.internal.util.privilegedactions.GetMethod"}, +{"class": "org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters"}, +{"class": "org.hibernate.validator.resourceloading.PlatformResourceBundleLocator"}, +{"package": "org.hibernate.validator.internal.util.logging.LoggerFactory"}, +{"package": "ch.qos.logback.core"}, +{"package": "ch.qos.logback.classic"}, +{"package": "ch.qos.logback.classic.util"}, +{"class": "ch.qos.logback.classic.util.LogbackMDCAdapter"}, +{"class": "ch.qos.logback.classic.spi.LoggingEvent"}, +{"class": "ch.qos.logback.core.helpers.CyclicBuffer"}, +{"class": "ch.qos.logback.core.encoder.LayoutWrappingEncoder"}, +{"class": "ch.qos.logback.core.joran.spi.ConsoleTarget$1"}, +{"class": "ch.qos.logback.core.ConsoleAppender"}, +{"class": "ch.qos.logback.core.spi.AppenderAttachableImpl"}, +{"class": "ch.qos.logback.classic.BasicConfigurator"}, +{"class": "org.slf4j.MDC"}, +{"class": "com.rabbitmq.client.SocketChannelConfigurator"} +// See https://medium.com/graalvm/instant-netty-startup-using-graalvm-native-image-generation-ed6f14ff7692 +] +} + + diff --git a/src/main/resources/proxies.json b/src/main/resources/proxies.json new file mode 100644 index 000000000..8a9f71042 --- /dev/null +++ b/src/main/resources/proxies.json @@ -0,0 +1,26 @@ +[ +["org.springframework.boot.context.properties.ConfigurationProperties","org.springframework.core.annotation.SynthesizedAnnotation"], +["org.springframework.stereotype.Component"], +["org.springframework.beans.factory.annotation.Qualifier"], +["org.springframework.boot.context.properties.ConfigurationProperties"], +["org.springframework.context.annotation.Lazy"], +["org.springframework.web.bind.annotation.RequestMapping"], +["org.springframework.web.bind.annotation.ResponseStatus"], +["org.springframework.web.bind.annotation.RequestBody"], +["org.springframework.web.bind.annotation.ResponseBody"], +["javax.validation.Validator","org.springframework.aop.SpringProxy","org.springframework.aop.framework.Advised","org.springframework.core.DecoratingProxy"], +["org.hibernate.jpa.HibernateEntityManagerFactory","org.springframework.orm.jpa.EntityManagerFactoryInfo"], +["org.hibernate.jpa.HibernateEntityManagerFactory","org.springframework.orm.jpa.EntityManagerFactoryInfo","javax.persistence.EntityManagerFactory"], +["javax.persistence.Id"], +["javax.persistence.GeneratedValue"], +["javax.persistence.Transient"], +["org.springframework.data.jpa.repository.support.CrudMethodMetadata", "org.springframework.aop.SpringProxy", "org.springframework.aop.framework.Advised", "org.springframework.core.DecoratingProxy"], +["org.hibernate.jpa.HibernateEntityManager","org.springframework.orm.jpa.EntityManagerProxy"], +["org.springframework.web.bind.annotation.RequestParam"], +["org.springframework.boot.CommandLineRunner","app.main.Finder","org.springframework.aop.SpringProxy", + "org.springframework.aop.framework.Advised","org.springframework.core.DecoratingProxy"], +["app.main.model.FooRepository","org.springframework.aop.SpringProxy","org.springframework.aop.framework.Advised", + "org.springframework.core.DecoratingProxy"], + ["org.springframework.amqp.rabbit.annotation.RabbitListener"], + ["org.springframework.amqp.rabbit.connection.ChannelProxy"] +] diff --git a/src/main/resources/reflect.json b/src/main/resources/reflect.json new file mode 100644 index 000000000..c80e85bce --- /dev/null +++ b/src/main/resources/reflect.json @@ -0,0 +1,1689 @@ +[ + // vv vanilla-grpc + { + "name": "com.google.protobuf.ExtensionRegistry", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // ^^ vanilla-grpc + // These two are from CodecConfigurer.properties + { + "name": "org.springframework.http.codec.support.DefaultClientCodecConfigurer", + "methods": [{"name": "","parameterTypes": []}] + }, + { + "name": "org.springframework.http.codec.support.DefaultServerCodecConfigurer", + "methods": [{"name": "","parameterTypes": []}] + }, + { + "name": "org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.annotations.common.Version", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.AutoFlushEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.OnResourceCondition", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.ClearEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.DeleteEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.DirtyCheckEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.EvictEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.hibernate.event.spi.FlushEntityEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.FlushEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.InitializeCollectionEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.LoadEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.LockEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.MergeEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PersistEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostCollectionRecreateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostCollectionRemoveEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostCollectionUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostDeleteEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostInsertEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostLoadEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreCollectionRecreateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreCollectionRemoveEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreCollectionUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreDeleteEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreInsertEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreLoadEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.RefreshEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.ReplicateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.ResolveNaturalIdEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.SaveOrUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.hibernate.event.spi.EventType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.dialect.H2Dialect", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.jboss.logging.BasicLogger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.sun.xml.internal.stream.events.XMLEventFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.annotations.common.util.impl.Log_$logger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.annotations.common.util.impl.Log", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.sun.xml.internal.stream.events.XMLEventFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "com.google.gson.GsonBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.Advised", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // these 3 from AopConfigUtils + { + "name": "org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.aspectj.AspectJAwareAdvisorAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // supertypes of InfrastructureAdvistorAutoProxyCreator - to combat error: + // Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'order' of + // bean class [org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator]: Bean property + // 'order' is not writable or has an invalid setter method. Does the parameter type + // of the setter match the return type of the getter? + { + "name": "org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.ProxyProcessorSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.ProxyConfig", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // Should these two be pulled in by annotations on entityManagerFactory method in JpaBaseConfiguration + { + "name": "javax.persistence.EntityManagerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.jpa.HibernateEntityManagerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.hibernate.validator.HibernateValidatorConfiguration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Configuration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.SpringBootVersion", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.validation.spi.ConfigurationState", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.validation.Configuration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.http.client.reactive.ReactorResourceFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.function.client.WebClient$Builder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.client.RestTemplateBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext", + "methods": [ + { + "name": "", + "parameterTypes": [ + ] + } + ] + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesBinder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.SearchStrategy", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.support.PropertySourcesPlaceholderConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ScopedProxyMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.jmx.export.naming.MetadataNamingStrategy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.CommonAnnotationBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.util.logging.LogManager", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredClasses": true + }, + { + "name": "org.springframework.boot.logging.java.JavaLoggingSystem", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredClasses": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication$Type", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication$Type", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigureOrder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // these 5 are from BaseDefaultCodecs + { + "name": "com.fasterxml.jackson.databind.ObjectMapper", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.fasterxml.jackson.core.JsonGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.fasterxml.jackson.dataformat.smile.SmileFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Message", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.xml.bind.Binder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // this one after adding those 5 + { + "name": "com.sun.xml.internal.stream.XMLInputFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.AnnotationConfigApplicationContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.ConditionalOnResource", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.GetMapping", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.RequestMapping", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.PostMapping", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.ResponseBody", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.RequestBody", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.stereotype.Controller", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.RestController", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Role", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.validation.Validator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.validation.Validator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ImportSelector", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.sun.jmx.mbeanserver.JmxMBeanServer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.event.DefaultEventListenerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Lazy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.ClassLoader", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationProperties", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.TypeExcludeFilter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigureAfter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.jmx.support.MBeanRegistrationSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.BeanFactoryAware", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.cache.CacheCondition", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.CommandLineRunner", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.SpringBootConfiguration", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.SpringBootApplication", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.sun.jmx.mbeanserver.SunJmxMBeanServer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.xml.XmlBeanDefinitionReader", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.config.DelegatingWebFluxConfiguration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.config.EnableWebFlux", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.EnableAutoConfiguration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + },{ + "name": "org.springframework.beans.factory.config.PropertiesFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + },{ + "name": "org.springframework.core.io.support.PropertiesLoaderSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackage", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.event.EventListenerMethodProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ComponentScan", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.reflect.ParameterizedType", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.task.TaskExecutorBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.EnableConfigurationProperties", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ImportBeanDefinitionRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.SpringApplication", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ComponentScan$Filter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration$ConfigAvailableCondition", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // vanilla-thymeleaf related, starts here... + { + "name": "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.http.converter.FormHttpMessageConverter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, // NPE: org.springframework.boot.autoconfigure.http.HttpMessageConverters.extractPartConverters(HttpMessageConverters.java:162) + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.RequestParam", + "allDeclaredConstructors": true, + "allDeclaredFields": true, // Method [public abstract java.lang.String org.springframework.web.bind.annotation.RequestParam.name()] is unsupported for synthesized annotation type [interface org.springframework.web.bind.annotation.RequestParam] + "allDeclaredMethods": true + }, + { + "name": "org.thymeleaf.spring5.view.reactive.ThymeleafReactiveView", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.thymeleaf.spring5.expression.Mvc$NonSpring41MvcUriComponentsBuilderDelegate", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.thymeleaf.standard.expression.AdditionExpression", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // ends here... + // vanilla-tx vvv + { + "name": "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.jdbc.support.SQLErrorCodes", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.sql.DatabaseMetaData", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.transaction.TransactionDefinition", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.transaction.annotation.Propagation", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.transaction.annotation.Transactional", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.AdviceMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.AutoProxyRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // vanilla-tx ^^^ + { + "name": "org.springframework.boot.autoconfigure.jmx.ParentAwareNamingStrategy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration$JCacheAvailableCondition", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.task.TaskSchedulerBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.validation.beanvalidation.MethodValidationPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.HandlerResult", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Configuration", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.annotation.Autowired", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.AnnotationScopeMetadataResolver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.http.HttpMessageConverters", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.socket.nio.NioServerSocketChannel", + "methods": [ + { + "name": "", + "parameterTypes": [ + ] + } + ] + }, + { + "name": "io.netty.channel.socket.nio.NioSocketChannel", + "methods": [ + { + "name": "", + "parameterTypes": [ + ] + } + ] + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.http.codec.ClientCodecConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.http.codec.ServerCodecConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping$PreFlightAmbiguousMatchHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.util.logging.Messages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.util.logging.Log", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.BeanTypeRegistry", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.h2.Driver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.entity.SingleTableEntityPersister", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.cache.spi.access.CollectionDataAccess", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.mapping.PersistentClass", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.spi.PersisterCreationContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.cache.spi.access.NaturalIdDataAccess", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.cache.spi.access.EntityDataAccess", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.entity.AbstractEntityPersister", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.internal.PersisterClassResolverInitiator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.spi.PersisterClassResolver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.transaction.support.TransactionTemplate", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // These from DefaultIdentifierGeneratorFactory + { + "name": "org.hibernate.id.UUIDGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.GUIDGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.UUIDHexGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.Assigned", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.IdentityGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.SelectGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.enhanced.SequenceStyleGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.SequenceHiLoGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.IncrementGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.ForeignGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.SequenceIdentityGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.enhanced.TableGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "app.main.model.Foo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher$Registrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.jdbc.DataSourceProperties", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.config.EnableJpaRepositories", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.jpa.HibernateEntityManager", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // vvv vanilla-jpa + { + "name": "java.lang.Throwable", + "allDeclaredFields": true, + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.SessionImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.query.QueryLookupStrategy$Key", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.support.SimpleJpaRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { // this one shouldn't be needed (it's a superclass of the one below, which is what the bean definition actually is made of) + "name": "org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { // this one shouldn't be needed (it's a superclass of the one below, which is what the bean definition actually is made of) + "name": "org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.core.support.PropertiesBasedNamedQueries", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesScanRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesScan", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackage", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.persistence.Entity", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.persistence.GeneratedValue", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.persistence.Id", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurePackages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurePackages$BasePackages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.query.QueryByExampleExecutor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.PagingAndSortingRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.CrudRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.Repository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.JpaRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "app.main.model.FooRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.orm.jpa.SharedEntityManagerCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // ^^^ vanilla-jpa + { + "name": "org.springframework.orm.jpa.JpaVendorAdapter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.engine.resolver.JPATraversableResolver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.engine.resolver.TraversableResolvers", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.resourceloading.PlatformResourceBundleLocator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.engine.ConfigurationImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.annotation.Repeatable", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer$SharedMetadataReaderFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.management.ManagementFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.management.RuntimeMXBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.DefaultChannelPipeline$HeadContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.DefaultChannelPipeline$TailContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // vvv vanilla-grpc + { "name": "io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelInboundHandlerAdapter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelHandlerAdapter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelInboundHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.bootstrap.ServerBootstrap$1", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelInitializer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // ^^^ vanilla-grpc + + // Because of EntityTuplizerFactory + { + "name": "org.hibernate.tuple.Tuplizer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.tuple.entity.EntityTuplizer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.tuple.entity.AbstractEntityTuplizer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.tuple.entity.PojoEntityTuplizer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // { + // "name": "org.hibernate.tuple.entity.DynamicMapEntityTuplizer", + // "allDeclaredConstructors": true, + // "allDeclaredMethods": true + // }, + // { + // "name": "org.hibernate.tuple.entity.EntityMetamodel", + // "allDeclaredConstructors": true, + // "allDeclaredMethods": true + // }, + // { + // "name": "org.hibernate.mapping.PersistentClass", + // "allDeclaredConstructors": true, + // "allDeclaredMethods": true + // }, + + + { + "name": "javax.management.MBeanServer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.core.annotation.Order", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.support.GenericApplicationContext", + "methods": [ + { + "name": "", + "parameterTypes": [ + ] + } + ] + }, + { + "name": "org.springframework.context.event.GenericApplicationListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.jmx.export.annotation.AnnotationMBeanExporter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.config.BeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.annotation.ElementType", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.stereotype.Component", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.annotation.Qualifier", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ConfigurationClassPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.EnvironmentAware", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // jboss logging referenced in hibernate is accessing log4j: + { + "name": "org.apache.logging.log4j.message.ReusableMessageFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.apache.logging.log4j.message.DefaultFlowMessageFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.h2.mvstore.db.MVTableEngine", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.sql.Statement", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.sql.Statement[]", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "com.zaxxer.hikari.HikariDataSource", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.zaxxer.hikari.HikariConfig", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.EntityManagerMessageLogger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.EntityManagerMessageLogger_$logger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.CoreMessageLogger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.CoreMessageLogger_$logger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + // These 2 from HibernatJpaConfiguration: + { + "name": "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.service.jta.platform.internal.NoJtaPlatform", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.hibernate.Session", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "java.util.EventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.Class", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.util.EventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.Class", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.Object", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Bean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.handler.codec.http.HttpServerCodec", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.CombinedChannelDuplexHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.ChannelDuplexHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "reactor.netty.channel.ChannelOperationsHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "reactor.netty.http.server.HttpTrafficHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.annotation.RetentionPolicy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.yaml.snakeyaml.Yaml", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // Simple rabbit app + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties$Cache", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties$Listener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties$Ssl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties$Template", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.core.Queue", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name":"org.springframework.amqp.rabbit.annotation.RabbitListener", + "allDeclaredConstructors":true, + "allDeclaredMethods":true + }, + { + "name":"org.springframework.amqp.rabbit.connection.ChannelProxy", + "allDeclaredConstructors":true, + "allDeclaredMethods":true + }, + { + "name":"com.rabbitmq.client.Channel", + "allDeclaredConstructors":true, + "allDeclaredMethods":true + }, + { + "name":"com.rabbitmq.client.ShutdownNotifier", + "allDeclaredConstructors":true, + "allDeclaredMethods":true + }, + { + "name": "org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ConfigurationClassParser$DefaultDeferredImportSelectorGroup", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.rabbit.connection.CachingConnectionFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.core.AnonymousQueue", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.core.AmqpAdmin", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.reactivestreams.Publisher", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // plus spring integration + { + "name": "org.springframework.integration.config.IntegrationConfigurationBeanFactoryPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.integration.config.DefaultConfiguringBeanFactoryPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] diff --git a/src/main/resources/resources.json b/src/main/resources/resources.json new file mode 100644 index 000000000..fd33f88c4 --- /dev/null +++ b/src/main/resources/resources.json @@ -0,0 +1,73 @@ +{ + "resources": [ + {"pattern": "META-INF/spring.components"}, + {"pattern": "META-INF/spring.factories"}, + {"pattern": "org/springframework/boot/logging/java/logging.properties"}, + {"pattern": "META-INF/services/javax.validation.spi.ValidationProvider"}, // make pattern? + {"pattern": "hibernate.properties"}, + {"pattern": "org/hibernate/.*.xsd"}, + {"pattern": "org/hibernate/.*.dtd"}, + {"pattern": "application.yml"}, + {"pattern": "application.properties"}, + {"pattern": "logging.properties"}, + {"pattern": "org/springframework/http/codec/CodecConfigurer.properties"}, + + {"pattern": "org/reactivestreams/Publisher.class"}, + {"pattern": "org/springframework/amqp/rabbit/connection/ChannelProxy.class"}, + + // vvv vanilla-jpa + {"pattern": "org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.class"}, + {"pattern": "META-INF/jpa-named-queries.properties"}, + // ^^^ vanilla-jpa + + // vvv vanilla-tx + {"pattern": "org/springframework/boot/CommandLineRunner.class"}, + {"pattern": "app/main/EnableTx.class"}, + {"pattern": "org/springframework/context/annotation/AutoProxyRegistrar.class"}, + {"pattern": "app/main/Finder.class"}, + {"pattern": "org/springframework/jdbc/support/sql-error-codes.xml"}, + {"pattern": "schema.sql"}, + // ^^^ vanilla-tx + + // vvv vanilla-thymeleaf + {"pattern": "static/index.html"}, + {"pattern": "templates/greeting.html"}, + // ^^^ vanilla-thymeleaf + + // vvv vanilla-rabbit + {"pattern": "rabbitmq-amqp-client.properties"}, + // ^^^ vanilla-rabbit + {"pattern": "META-INF/spring.integration.default.properties"}, + + // Does this catch the logging//*.properties? + {"pattern": "org/springframework/boot/logging/.*.properties"}, + {"pattern": "org/springframework/boot/logging/.*.xml"}, + + {"pattern": "org/hibernate/validator/internal/engine/ConfigurationImpl.class"}, + {"pattern": "org/hibernate/validator/internal/util/Contracts.class"}, + {"pattern": "org/hibernate/validator/internal/util/logging/Log.class"}, + {"pattern": "org/hibernate/validator/internal/engine/resolver/TraversableResolvers.class"}, + + {"pattern": "org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.class"}, + {"pattern": "org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.Hikari.class"}, + {"pattern": "org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.Registrar.class"}, + {"pattern": "org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.class"}, + {"pattern": "org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.class"}, + {"pattern": "org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerPostProcessor.class"}, + + {"pattern": "org/springframework/context/ApplicationListener.class"}, + {"pattern": "org/springframework/beans/factory/InitializingBean.class"}, + {"pattern": "org/springframework/context/annotation/Role.class"}, + {"pattern": "javax/validation/Validator.class"}, + {"pattern": "org/springframework/context/annotation/Import.class"}, + {"pattern": "org/springframework/context/annotation/ImportAware.class"}, + {"pattern": "org/springframework/context/EnvironmentAware.class"}, + {"pattern": "org/springframework/context/annotation/Configuration.class"}, + {"pattern": "org/springframework/beans/factory/BeanFactoryAware.class"}, + {"pattern": "org/springframework/beans/factory/Aware.class"}, + {"pattern": "org/springframework/beans/factory/BeanClassLoaderAware.class"}, + {"pattern": "org/springframework/context/ApplicationContextAware.class"}, + {"pattern": "org/springframework/context/annotation/ImportBeanDefinitionRegistrar.class"} + ] +} + diff --git a/src/test/java/org/springframework/support/graal/TypeSystemTest.java b/src/test/java/org/springframework/support/graal/TypeSystemTest.java new file mode 100644 index 000000000..7430c718c --- /dev/null +++ b/src/test/java/org/springframework/support/graal/TypeSystemTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.support.graal; + +import java.io.File; + +import org.junit.Test; + +public class TypeSystemTest { + + @Test + public void test() throws Exception { + File file = new File("./target/classes"); + System.out.println(file.getCanonicalPath()); + } + +}