├── .gitignore ├── LICENSE.md ├── Readme.md ├── build.gradle ├── circle.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── schemagen-graphql-examples ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── bretpatterson │ └── schemagen │ └── graphql │ └── examples │ ├── HelloWorld.java │ ├── KeyValueStoreController.java │ └── common │ └── JacksonTypeFactory.java ├── schemagen-graphql-guava ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── bretpatterson │ └── schemagen │ └── graphql │ └── typemappers │ └── com │ └── google │ └── base │ └── OptionalMapper.java ├── schemagen-graphql-joda ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── bretpatterson │ └── schemagen │ └── graphql │ └── typemappers │ └── org │ └── joda │ ├── money │ └── MoneyMapper.java │ └── time │ └── DateTimeMapper.java ├── schemagen-graphql-spring ├── Readme.md ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── bretpatterson │ │ └── schemagen │ │ └── graphql │ │ ├── GraphQLSpringSchemaBuilder.java │ │ ├── annotations │ │ └── GraphQLSpringELDataFetcher.java │ │ └── datafetchers │ │ └── spring │ │ ├── SpringDataFetcher.java │ │ └── SpringDataFetcherFactory.java │ └── test │ └── java │ └── com │ └── bretpatterson │ └── schemagen │ └── graphq │ └── datafetchers │ └── spring │ ├── EchoBean.java │ ├── SpringDataFetcherFactoryTest.java │ └── TestModule.java ├── settings.gradle ├── src ├── main │ └── java │ │ └── com │ │ └── bretpatterson │ │ └── schemagen │ │ └── graphql │ │ ├── GraphQLSchemaBuilder.java │ │ ├── IDataFetcherFactory.java │ │ ├── IGraphQLObjectMapper.java │ │ ├── IGraphQLTypeCache.java │ │ ├── IMutationFactory.java │ │ ├── IQueryFactory.java │ │ ├── ITypeFactory.java │ │ ├── ITypeNamingStrategy.java │ │ ├── Introspection.java │ │ ├── annotations │ │ ├── GraphQLController.java │ │ ├── GraphQLDataFetcher.java │ │ ├── GraphQLDeprecated.java │ │ ├── GraphQLDescription.java │ │ ├── GraphQLIgnore.java │ │ ├── GraphQLMutation.java │ │ ├── GraphQLName.java │ │ ├── GraphQLParam.java │ │ ├── GraphQLQuery.java │ │ ├── GraphQLRequired.java │ │ ├── GraphQLTypeConverter.java │ │ └── GraphQLTypeMapper.java │ │ ├── datafetchers │ │ ├── CollectionConverterDataFetcher.java │ │ ├── DefaultMethodDataFetcher.java │ │ ├── DefaultTypeConverter.java │ │ ├── IDataFetcher.java │ │ ├── IMethodDataFetcher.java │ │ └── MapConverterDataFetcher.java │ │ ├── exceptions │ │ └── NotMappableException.java │ │ ├── impl │ │ ├── DefaultDataFetcherFactory.java │ │ ├── DefaultGraphQLTypeCache.java │ │ ├── DefaultQueryAndMutationFactory.java │ │ ├── FullTypeNamingStrategy.java │ │ ├── GraphQLObjectMapper.java │ │ ├── RelayTypeNamingStrategy.java │ │ ├── SimpleTypeFactory.java │ │ └── SimpleTypeNamingStrategy.java │ │ ├── relay │ │ ├── ConnectionCursor.java │ │ ├── Edge.java │ │ ├── INode.java │ │ ├── IRelayNodeFactory.java │ │ ├── IRelayNodeHandler.java │ │ ├── PageInfo.java │ │ ├── RelayConnection.java │ │ ├── annotations │ │ │ └── RelayNodeFactory.java │ │ ├── exceptions │ │ │ └── UnknownObjectType.java │ │ └── impl │ │ │ └── RelayDefaultNodeHandler.java │ │ ├── typemappers │ │ ├── IGraphQLTypeMapper.java │ │ ├── java │ │ │ ├── lang │ │ │ │ ├── CharSequenceMapper.java │ │ │ │ └── EnumMapper.java │ │ │ ├── math │ │ │ │ ├── BigDecimalMapper.java │ │ │ │ └── BigIntegerMapper.java │ │ │ ├── net │ │ │ │ └── URIMapper.java │ │ │ └── util │ │ │ │ ├── ArrayMapper.java │ │ │ │ ├── CollectionMapper.java │ │ │ │ ├── DateMapper.java │ │ │ │ ├── EnumSetMapper.java │ │ │ │ ├── MapMapper.java │ │ │ │ └── TimeZoneMapper.java │ │ └── relay │ │ │ └── ConnectionCursorMapper.java │ │ └── utils │ │ └── AnnotationUtils.java └── test │ └── java │ └── com │ └── bretpatterson │ └── schemagen │ └── graphql │ ├── GraphQLSchemaBuilderTest.java │ ├── impl │ ├── GraphQLObjectMapperTest.java │ ├── GraphQLObjectMapper_CollectionsTest.java │ ├── RelayTypeNamingStrategyTest.java │ ├── SimpleTypeNamingStrategyTest.java │ └── common │ │ └── JacksonTypeFactory.java │ ├── relay │ ├── IGameFactory.java │ ├── IUserFactory.java │ ├── RelayTest.java │ ├── controller │ │ ├── GameController.java │ │ ├── GameDTO.java │ │ └── UserDTO.java │ ├── dao │ │ └── GameDAO.java │ ├── manager │ │ └── GameManager.java │ └── model │ │ ├── IGame.java │ │ ├── IUser.java │ │ ├── PagedList.java │ │ ├── impl │ │ ├── Game.java │ │ └── User.java │ │ ├── payloads │ │ ├── AddUserToGameInput.java │ │ ├── AddUserToGamePayload.java │ │ ├── DeleteGameInput.java │ │ ├── DeleteGamePayload.java │ │ ├── DeleteUserInput.java │ │ ├── DeleteUserPayload.java │ │ ├── NewGameInput.java │ │ ├── NewGamePayload.java │ │ ├── NewUserInput.java │ │ └── NewUserPayload.java │ │ └── relay │ │ └── factories │ │ ├── RelayGameFactory.java │ │ └── RelayUserFactory.java │ ├── typemappers │ ├── RelayConnectionTypeMapper.java │ └── relay │ │ └── ConnectionCursorMapperTest.java │ └── utils │ └── AnnotationUtilsTest.java └── wiki ├── gliffy ├── highPlurality.gliffy └── sampleGraph.gliffy └── images ├── highPlurality.png └── sampleGraph.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | build/ 3 | .idea/ 4 | .gradle 5 | *.iml 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Bret Patterson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import java.text.SimpleDateFormat 2 | 3 | plugins { 4 | id "com.jfrog.bintray" version "1.4" 5 | } 6 | 7 | def getBintrayUser = project.properties.containsKey("bintrayUser") ? project["bintrayUser"] : "" 8 | def getBintrayApiKey = project.properties.containsKey("bintrayApiKey") ? project["bintrayApiKey"] : "" 9 | 10 | 11 | allprojects { 12 | description "This provides type introspection and generation of GraphQL from Pojos." 13 | group 'com.bretpatterson' 14 | 15 | def releaseVersion = project.properties.containsKey("releaseVersion") ? project["releaseVersion"]: "1.1.0-SNAPSHOT" 16 | 17 | apply plugin: 'java' 18 | apply plugin: 'jacoco' 19 | apply plugin: 'maven' 20 | apply plugin: 'signing' 21 | apply plugin: 'maven-publish' 22 | apply plugin: 'com.jfrog.bintray' 23 | 24 | ext { 25 | jacksonVersion = "2.5.1"; 26 | graphQLJavaVersion = "2.4.0"; 27 | jacocoReportsDir = "$buildDir/reports/jacoco" 28 | junitVersion = "4.12" 29 | mockitoVersion = "1.9.5" 30 | } 31 | 32 | version = releaseVersion ? releaseVersion : new SimpleDateFormat('yyyy-MM-dd\'T\'HH-mm-ss').format(new Date()) 33 | 34 | jar { 35 | from "${project.rootDir}/LICENSE.md" 36 | } 37 | 38 | defaultTasks "compileJava", "test", "jacocoTestReport" 39 | 40 | 41 | jacocoTestReport { 42 | dependsOn build 43 | reports { 44 | xml.enabled false 45 | csv.enabled false 46 | html.destination jacocoReportsDir 47 | } 48 | } 49 | jacoco { 50 | toolVersion = "0.7.6.201602180812" 51 | } 52 | 53 | test { 54 | jacoco { 55 | append = false 56 | destinationFile = file("$buildDir/jacoco/jacocoTest.exec") 57 | classDumpFile = file("$buildDir/jacoco/classpathdumps") 58 | } 59 | } 60 | 61 | task sourcesJar(type: Jar) { 62 | dependsOn classes 63 | classifier 'sources' 64 | from sourceSets.main.allSource 65 | } 66 | 67 | task javadocJar(type: Jar, dependsOn: javadoc) { 68 | classifier = 'javadoc' 69 | from javadoc.destinationDir 70 | } 71 | 72 | artifacts { 73 | archives file("build/libs/${project.name}-" + project.version + ".jar") 74 | archives sourcesJar 75 | archives javadocJar 76 | } 77 | 78 | repositories { 79 | mavenLocal(); 80 | mavenCentral(); 81 | maven { 82 | url "https://dl.bintray.com/andimarek/graphql-java" 83 | } 84 | } 85 | 86 | publishing { 87 | publications { 88 | schemagenGraphQL(MavenPublication) { 89 | version version 90 | from components.java 91 | 92 | artifact sourcesJar { 93 | classifier "sources" 94 | } 95 | artifact javadocJar { 96 | classifier "javadoc" 97 | } 98 | pom.withXml { 99 | asNode().children().last() + { 100 | resolveStrategy = Closure.DELEGATE_FIRST 101 | name project.name 102 | description project.description 103 | 104 | url "https://github.com/bpatters/schemagen-graphql" 105 | scm { 106 | url "https://github.com/bpatters/schemagen-graphql" 107 | connection "https://github.com/bpatters/schemagen-graphql" 108 | developerConnection "https://github.com/bpatters/schemagen-graphql" 109 | } 110 | licenses { 111 | license { 112 | name 'MIT' 113 | url 'https://github.com/bpatters/schemagen-graphql/blob/master/LICENSE.md' 114 | distribution 'repo' 115 | } 116 | } 117 | developers { 118 | developer { 119 | id 'bpatters' 120 | name 'Bret Patterson' 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | bintray { 130 | user = getBintrayUser 131 | key = getBintrayApiKey 132 | publications = ['schemagenGraphQL'] 133 | publish = true 134 | pkg { 135 | repo = 'schemagen-graphql' 136 | name = 'schemagen-graphql' 137 | desc = 'GraphQL Schema generation in Java' 138 | licenses = ['MIT'] 139 | vcsUrl = 'https://github.com/bpatters/schemagen-graphql' 140 | } 141 | } 142 | 143 | sourceCompatibility = 1.7 144 | targetCompatibility = 1.7 145 | } 146 | 147 | 148 | dependencies { 149 | compile ("com.graphql-java:graphql-java:${graphQLJavaVersion}") 150 | compile 'org.slf4j:slf4j-api:1.7.12' 151 | compile 'com.google.guava:guava:19.0' 152 | 153 | testCompile "junit:junit:${junitVersion}" 154 | testCompile "org.mockito:mockito-all:${mockitoVersion}" 155 | testCompile("com.fasterxml.jackson.core:jackson-core:${jacksonVersion}") 156 | testCompile("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}") 157 | } 158 | 159 | 160 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | java: 3 | version: oraclejdk8 4 | services: 5 | - docker 6 | dependencies: 7 | override: 8 | - ./gradlew build 9 | test: 10 | override: 11 | - ./gradlew test 12 | deployment: 13 | # disable until ready for production deploy 14 | prod: 15 | tag: /^\d+\.\d+\.\d+$/ 16 | commands: 17 | - ./gradlew -PbintrayUser=$bintrayUser -PbintrayApiKey=$bintrayApiKey -PreleaseVersion=$CIRCLE_TAG clean bintray 18 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | bintray.user=DUMMY_USER 2 | bintray.key=DUMMY_KEY -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpatters/schemagen-graphql/6f16fac5e01bfe97deeebb00bd2726bb5c3403f5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 21 14:09:32 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /schemagen-graphql-examples/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | dependencies { 4 | compile(project(':')) { 5 | transitive = true 6 | } 7 | compile("com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"); 8 | compile("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"); 9 | testCompile group: 'junit', name: 'junit', version: '4.11' 10 | } 11 | 12 | publishing { 13 | publications { 14 | schemagenGraphQLExamples(MavenPublication) { 15 | version version 16 | from components.java 17 | 18 | artifact sourcesJar { 19 | classifier "sources" 20 | } 21 | artifact javadocJar { 22 | classifier "javadoc" 23 | } 24 | pom.withXml { 25 | asNode().children().last() + { 26 | resolveStrategy = Closure.DELEGATE_FIRST 27 | name project.name 28 | description project.description 29 | 30 | url "https://github.com/bpatters/schemagen-graphql" 31 | scm { 32 | url "https://github.com/bpatters/schemagen-graphql" 33 | connection "https://github.com/bpatters/schemagen-graphql" 34 | developerConnection "https://github.com/bpatters/schemagen-graphql" 35 | } 36 | licenses { 37 | license { 38 | name 'MIT' 39 | url 'https://github.com/bpatters/schemagen-graphql/blob/master/LICENSE.md' 40 | distribution 'repo' 41 | } 42 | } 43 | developers { 44 | developer { 45 | id 'bpatters' 46 | name 'Bret Patterson' 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | bintray { 56 | publications = ['schemagenGraphQLExamples'] 57 | publish = true 58 | pkg { 59 | repo = 'schemagen-graphql' 60 | name = 'schemagen-graphql-examples' 61 | desc = 'Examples GraphQL Schema generation in Java' 62 | licenses = ['MIT'] 63 | vcsUrl = 'https://github.com/bpatters/schemagen-graphql' 64 | } 65 | } -------------------------------------------------------------------------------- /schemagen-graphql-examples/src/main/java/com/bretpatterson/schemagen/graphql/examples/HelloWorld.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.examples; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLQuery; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLController; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.google.common.collect.ImmutableList; 7 | import com.bretpatterson.schemagen.graphql.GraphQLSchemaBuilder; 8 | import com.bretpatterson.schemagen.graphql.examples.common.JacksonTypeFactory; 9 | 10 | import graphql.ExecutionResult; 11 | import graphql.GraphQL; 12 | import graphql.schema.GraphQLSchema; 13 | 14 | /** 15 | * This is a hello world example of a graphQL query CLI 16 | */ 17 | @GraphQLController 18 | public class HelloWorld { 19 | 20 | @GraphQLQuery(name="helloWorld") 21 | public String helloWorld() { 22 | 23 | return "Hello World!"; 24 | } 25 | 26 | public static void main(String[] args) throws Exception { 27 | // the object that we want to expose it's methods as queries 28 | HelloWorld helloWorld = new HelloWorld(); 29 | // we use a Jackson object mapper object for serialization of values 30 | ObjectMapper objectMapper = new ObjectMapper(); 31 | GraphQLSchema schema = GraphQLSchemaBuilder.newBuilder() 32 | // register an object mappper so that parameter datatypes can be deserialized for method invocation 33 | .registerTypeFactory(new JacksonTypeFactory(objectMapper)) 34 | // register the instance of Hello World as our query handler 35 | .registerGraphQLControllerObjects(ImmutableList.of(helloWorld)) 36 | .build(); 37 | 38 | String queryString = "{ helloWorld }"; 39 | 40 | if (args.length > 0) { 41 | queryString = args[0]; 42 | } 43 | 44 | // now lets execute a query against the schema 45 | ExecutionResult result = new GraphQL(schema).execute(queryString); 46 | if (result.getErrors().size() != 0) { 47 | // if there are any errors serialize them using jackson and write them to stderr 48 | System.err.println(objectMapper.writeValueAsString(result.getErrors())); 49 | } else { 50 | // output your response as JSON serialized data 51 | System.out.println(objectMapper.writeValueAsString(result.getData())); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /schemagen-graphql-examples/src/main/java/com/bretpatterson/schemagen/graphql/examples/KeyValueStoreController.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.examples; 2 | 3 | import com.bretpatterson.schemagen.graphql.GraphQLSchemaBuilder; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLController; 5 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLIgnore; 6 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLMutation; 7 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLParam; 8 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLQuery; 9 | import com.bretpatterson.schemagen.graphql.examples.common.JacksonTypeFactory; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.google.common.collect.ImmutableList; 12 | import com.google.common.collect.Maps; 13 | import graphql.ExecutionResult; 14 | import graphql.GraphQL; 15 | import graphql.schema.GraphQLSchema; 16 | 17 | import java.io.BufferedReader; 18 | import java.io.InputStreamReader; 19 | import java.util.Map; 20 | 21 | /** 22 | * This is a hello world example of a graphQL query CLI 23 | */ 24 | @GraphQLController 25 | public class KeyValueStoreController { 26 | 27 | @GraphQLIgnore 28 | private Map dataStore = Maps.newHashMap(); 29 | 30 | @GraphQLQuery(name = "get") 31 | public String getValue(@GraphQLParam(name = "key") String key) { 32 | return dataStore.get(key); 33 | } 34 | 35 | @GraphQLMutation(name = "put") 36 | public String setValue(@GraphQLParam(name = "key") String key, @GraphQLParam(name = "value") String value) { 37 | dataStore.put(key, value); 38 | 39 | return value; 40 | } 41 | 42 | public static void main(String[] args) throws Exception { 43 | // the object that we want to expose it's methods as queries 44 | KeyValueStoreController keyValueStoreController = new KeyValueStoreController(); 45 | // we use a Jackson object mapper object for serialization of values 46 | ObjectMapper objectMapper = new ObjectMapper(); 47 | GraphQLSchema schema = GraphQLSchemaBuilder.newBuilder() 48 | // register an object mappper so that parameter datatypes can be deserialized for method invocation 49 | .registerTypeFactory(new JacksonTypeFactory(objectMapper)) 50 | // register the instance of Hello World as our query handler 51 | .registerGraphQLControllerObjects(ImmutableList. of(keyValueStoreController)) 52 | .build(); 53 | 54 | BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 55 | do { 56 | 57 | System.out.print("Query> "); 58 | String queryString = br.readLine(); 59 | if ("quit".equals(queryString) || queryString.length() == 0) { 60 | break; 61 | } 62 | 63 | // now lets execute a query against the schema 64 | ExecutionResult result = new GraphQL(schema).execute(queryString); 65 | if (result.getErrors().size() != 0) { 66 | // if there are any errors serialize them using jackson and write them to stderr 67 | System.err.println(objectMapper.writeValueAsString(result.getErrors())); 68 | } else { 69 | // output your response as JSON serialized data 70 | System.out.println(objectMapper.writeValueAsString(result.getData())); 71 | } 72 | } while (true); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /schemagen-graphql-examples/src/main/java/com/bretpatterson/schemagen/graphql/examples/common/JacksonTypeFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.examples.common; 2 | 3 | import com.bretpatterson.schemagen.graphql.ITypeFactory; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.type.TypeFactory; 6 | import com.google.common.base.Throwables; 7 | 8 | import java.io.IOException; 9 | import java.lang.reflect.Type; 10 | 11 | /** 12 | * A very simple object mapper that uses Jackson Json serialization 13 | */ 14 | public class JacksonTypeFactory implements ITypeFactory { 15 | ObjectMapper objectMapper; 16 | 17 | public JacksonTypeFactory(ObjectMapper objectMapper) { 18 | this.objectMapper = objectMapper; 19 | } 20 | 21 | @Override 22 | public Object convertToType(Type type, Object arg) { 23 | try { 24 | // Here we simply use Jackson object mapper to first write the GraphQL generic structures to a string 25 | // then we read the string back in using jackson telling it to convert it to the specified type. 26 | return objectMapper.readValue(objectMapper.writeValueAsString(arg), TypeFactory.defaultInstance().constructType(type)); 27 | } catch(IOException ex) { 28 | return Throwables.propagate(ex); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /schemagen-graphql-guava/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile(project(':')) { 3 | transitive = true 4 | } 5 | testCompile "junit:junit:version:${junitVersion}" 6 | } 7 | 8 | 9 | publishing { 10 | publications { 11 | schemagenGraphQLGuava(MavenPublication) { 12 | version version 13 | from components.java 14 | 15 | artifact sourcesJar { 16 | classifier "sources" 17 | } 18 | artifact javadocJar { 19 | classifier "javadoc" 20 | } 21 | pom.withXml { 22 | asNode().children().last() + { 23 | resolveStrategy = Closure.DELEGATE_FIRST 24 | name project.name 25 | description project.description 26 | 27 | url "https://github.com/bpatters/schemagen-graphql" 28 | scm { 29 | url "https://github.com/bpatters/schemagen-graphql" 30 | connection "https://github.com/bpatters/schemagen-graphql" 31 | developerConnection "https://github.com/bpatters/schemagen-graphql" 32 | } 33 | licenses { 34 | license { 35 | name 'MIT' 36 | url 'https://github.com/bpatters/schemagen-graphql/blob/master/LICENSE.md' 37 | distribution 'repo' 38 | } 39 | } 40 | developers { 41 | developer { 42 | id 'bpatters' 43 | name 'Bret Patterson' 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | bintray { 53 | publications = ['schemagenGraphQLGuava'] 54 | publish = true 55 | pkg { 56 | repo = 'schemagen-graphql' 57 | name = 'schemagen-graphql-guava' 58 | desc = 'Guava support for GraphQL Schema generation in Java' 59 | licenses = ['MIT'] 60 | vcsUrl = 'https://github.com/bpatters/schemagen-graphql' 61 | } 62 | } -------------------------------------------------------------------------------- /schemagen-graphql-guava/src/main/java/com/bretpatterson/schemagen/graphql/typemappers/com/google/base/OptionalMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.com.google.base; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import com.google.common.base.Optional; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | 10 | import java.lang.reflect.ParameterizedType; 11 | import java.lang.reflect.Type; 12 | 13 | /** 14 | * Created by bpatterson on 1/21/16. 15 | */ 16 | @GraphQLTypeMapper(type= Optional.class) 17 | public class OptionalMapper implements IGraphQLTypeMapper { 18 | @Override 19 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 20 | if (type instanceof ParameterizedType) { 21 | ParameterizedType parameterizedType = (ParameterizedType) type; 22 | 23 | return parameterizedType.getRawType() == Optional.class; 24 | } 25 | 26 | return false; 27 | } 28 | 29 | @Override 30 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper objectMapper, Type type) { 31 | ParameterizedType parameterizedType = (ParameterizedType) type; 32 | return objectMapper.getOutputType(parameterizedType.getActualTypeArguments()[0]); 33 | } 34 | 35 | @Override 36 | public GraphQLInputType getInputType(IGraphQLObjectMapper objectMapper, Type type) { 37 | ParameterizedType parameterizedType = (ParameterizedType) type; 38 | return objectMapper.getInputType(parameterizedType.getActualTypeArguments()[0]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /schemagen-graphql-joda/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile(project(':')) { 3 | transitive = true 4 | } 5 | compile 'joda-time:joda-time:2.3' 6 | compile 'org.joda:joda-money:0.9' 7 | testCompile "junit:junit:version:${junitVersion}" 8 | } 9 | 10 | publishing { 11 | publications { 12 | schemagenGraphQLJoda(MavenPublication) { 13 | version version 14 | from components.java 15 | 16 | artifact sourcesJar { 17 | classifier "sources" 18 | } 19 | artifact javadocJar { 20 | classifier "javadoc" 21 | } 22 | pom.withXml { 23 | asNode().children().last() + { 24 | resolveStrategy = Closure.DELEGATE_FIRST 25 | name project.name 26 | description project.description 27 | 28 | url "https://github.com/bpatters/schemagen-graphql" 29 | scm { 30 | url "https://github.com/bpatters/schemagen-graphql" 31 | connection "https://github.com/bpatters/schemagen-graphql" 32 | developerConnection "https://github.com/bpatters/schemagen-graphql" 33 | } 34 | licenses { 35 | license { 36 | name 'MIT' 37 | url 'https://github.com/bpatters/schemagen-graphql/blob/master/LICENSE.md' 38 | distribution 'repo' 39 | } 40 | } 41 | developers { 42 | developer { 43 | id 'bpatters' 44 | name 'Bret Patterson' 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | bintray { 54 | publications = ['schemagenGraphQLJoda'] 55 | publish = true 56 | pkg { 57 | repo = 'schemagen-graphql' 58 | name = 'schemagen-graphql-joda' 59 | desc = 'Joda support for GraphQL Schema generation in Java' 60 | licenses = ['MIT'] 61 | vcsUrl = 'https://github.com/bpatters/schemagen-graphql' 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /schemagen-graphql-joda/src/main/java/com/bretpatterson/schemagen/graphql/typemappers/org/joda/money/MoneyMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.org.joda.money; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.Scalars; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | import org.joda.money.Money; 10 | 11 | import java.lang.reflect.Type; 12 | 13 | 14 | /** 15 | * Created by bpatterson on 1/19/16. 16 | */ 17 | @GraphQLTypeMapper(type=Money.class) 18 | public class MoneyMapper implements IGraphQLTypeMapper { 19 | @Override 20 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 21 | return type == Money.class; 22 | } 23 | 24 | @Override 25 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper objectMapper, Type type) { 26 | return Scalars.GraphQLFloat; 27 | } 28 | 29 | @Override 30 | public GraphQLInputType getInputType(IGraphQLObjectMapper objectMapper, Type type) { 31 | return Scalars.GraphQLFloat; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /schemagen-graphql-joda/src/main/java/com/bretpatterson/schemagen/graphql/typemappers/org/joda/time/DateTimeMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.org.joda.time; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.Scalars; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | import org.joda.time.DateTime; 10 | 11 | import java.lang.reflect.Type; 12 | 13 | /** 14 | * Created by bpatterson on 1/19/16. 15 | */ 16 | @GraphQLTypeMapper(type=DateTime.class) 17 | public class DateTimeMapper implements IGraphQLTypeMapper { 18 | @Override 19 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 20 | return type == DateTime.class; 21 | } 22 | 23 | @Override 24 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper objectMapper, Type type) { 25 | return Scalars.GraphQLString; 26 | } 27 | 28 | @Override 29 | public GraphQLInputType getInputType(IGraphQLObjectMapper objectMapper, Type type) { 30 | return Scalars.GraphQLString; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /schemagen-graphql-spring/Readme.md: -------------------------------------------------------------------------------- 1 | This module includes a Spring Expression Language (EL) Datafetcher. 2 | 3 | ---- 4 | 5 | The primary purpose of this Data fetcher is to enable us bring the concept of Hibernate Lazy Fetches to GraphQL. 6 | 7 | Let's assume you are going to implement a Calendar application that has two views, A month view and a Week view of the following graph: 8 | 9 | ![](https://github.com/bpatters/schemagen-graphql/blob/master/wiki/images/sampleGraph.png?raw=true) 10 | 11 | In the graph above there are several 0..* potentially high plurality relationships. Let's consider the first relationship 12 | 13 | Calendar ----> CalendarItem 14 | 15 | 16 | This is what we call a top level relationship, or in other words it's an entrypoint into the graph. 17 | For these you expose a query field on the top level static Calendar object like: 18 | 19 | @GraphQuery(name="calendarItems") 20 | List getCalendarItems(@GraphQLParam("startDate") Date startDate, @GraphQLParam("endDate") Data endDate); 21 | 22 | This query can be used for both the week view, and the month view. It allows us to fetch all the calendar items for a specified date range. 23 | 24 | Month View 25 | 26 | ---- 27 | 28 | Now let's say you want to populate a Month view, and due to limited space you only want to display two fields: 29 | * Title 30 | * Date and time of the meeting 31 | 32 | To do this you would use the following GraphQL Query 33 | 34 | query { 35 | Calendar { 36 | calendarItems(startDate:"1/1/2016", endDate:"1/31/2016") { 37 | title 38 | dateAndTime 39 | } 40 | } 41 | } 42 | 43 | 44 | This would call the getCalendarItems endpoint and return only the title and dateTime fields for all calendar items. Not bad, if you had a 100 calendar items for the month and 50 guests for each calendar item (you're a popular person!) you would get a list of JSON response payload of 100 calendar items like: 45 | 46 | { "data": [ {"title":"title1", "dateAndTime":"1/1/2016"} ... ] } 47 | 48 | 49 | This would be a reasonable amount of data, and the minimal set, to return from the Backend to the UI. However, let's look a bit deeper on the backend and what it has to do to solve this request. Let's assume you have the following two classes representing the CalendarItem and it's Guests (users): 50 | 51 | public class CalendarItemDTO { 52 | String id; 53 | String title; 54 | DateTime dateAndTime; 55 | User owner; 56 | List guests; 57 | } 58 | 59 | public class UserDTO { 60 | String id; 61 | String firstName; 62 | String lastName; 63 | String email; 64 | } 65 | 66 | 67 | When the method is executed it is going to call some business methods which will return a List object which will be used to populate these DTO's. The GraphQL layer will then extract the fields requested and return them to the UI. The problem here is the business layer has no idea what fields the UI is going to request. GraphQL asks for the calendarItems and then goes through each field the UI requested and extracts them individually. Short of passing the query field graph to the business object, which would be extreme, we don't really have an easy way to explain to the business layer exactly what the UI is going to use from the result set. As such we have to fall back to conventions. 68 | 69 | The simplest convention is the business layer fetches everything and fully populates all DTO's, and their relationships, then lets the GraphQL layer reduce it before sending it to the UI. This works, but it still requires the backend to do an awful lot of extra data fetching. What if you had 100 calendar items in the month, with 50 guests each? That's 5000 User records you'd be fetching even though for the month view query we don't need any of the Guest information!!! 70 | 71 | Ideally what we would want is for the backend to only fetch the *small and cheap to fetch data* and save the *large and expensive to fetch* data until we know for sure it will be used. The way we do this in Schemagen-GraphQL is by being lazy where it counts. By convention we want to: 72 | 73 | > Only fetch low plurality fields by default and lazy fetch the high plurality fields 74 | 75 | This means by convention anything that is a Collection, ie List, we don't populate by default but instead fetch only when explicitely asked to. 76 | How does this happen? It requires two things: 77 | 1. Implement your business layer to only fetch the low plurality fields aka primitive fields and 1-to-1 relationship fields (you can further optimize by making all relationships lazy, but for this example we stick to high plurality fields). 78 | 2. Annotate your high plurality fields in the DTO's with a DataFetcher that supports lazy data fetching 79 | 80 | Currently, the only data fetcher that supports this property is: 81 | 82 | GraphQLSpringELDataFetcher 83 | 84 | This data fetcher allows you specify a Spring Expression Language expression that knows how to fetch the relationship that it annotates. For example, let's say you have a Spring bean that you've registered that knows how to fetch Guests for a specific calendar item. Let's call this bean: 85 | 86 | @Component("calendarManager") 87 | public class CalendarManager { 88 | List getCalendarItemGuests(String calendarItemId); 89 | } 90 | 91 | 92 | *This bean could also double as your root Calendar GraphController, which is very useful to be able to reuse it's functions* 93 | 94 | We could then annotate the CalendarItemDTO for lazy fetching with: 95 | 96 | public class CalendarItemDTO { 97 | String id; 98 | String title; 99 | DateTime dateAndTime; 100 | User owner; 101 | List guests; 102 | 103 | @GraphQLSpringELDataFetcher ("@calendarManager.getCalendarItemGuests(#this.id)") 104 | List getGuests() { 105 | throw new IllegalArgument("Data fetcher failed, this property is lazy fetched!"); 106 | } 107 | } 108 | 109 | Now we have a backend that will never fetch a calendar items guest list unless the UI specifically requests it, in that scenario we will lazily fetch the guest list indirectly via the execution of the Spring EL expression. Let's break the expression down, in case your not familiar with it's usage. 110 | 111 | * @calendarManager The @ means that the variable after it is the name of a Registered Bean. In this case *calendarManager* 112 | * .getCalenarItemGuests specifies the method to execute. 113 | * (#this.id) - is the parameters to pass to the method. #this is pre-defined variable representing the wrapping object, in this case the Calendar Item and we use it to pass in the CalendarItem's ID field to the method. 114 | 115 | That's it, you now have a lazy fetching enabled Graph! 116 | 117 | # Implementation Steps 118 | 119 | * Include schemagen-graphql-spring in your dependencies. 120 | * When building your schema register the SpringDataFetcherFactory as your default data fetching factory. *this adds support for detection of the @GraphQLSpringELDataFetcher annotation during schema compilation.* 121 | 122 | GraphQLSchemaBuilder.registerDataFetcherFactory(new SpringELDataFetcherFactory()); 123 | 124 | * Annotate the methods you want to be lazily fetch with a spring expression data fetcher. -------------------------------------------------------------------------------- /schemagen-graphql-spring/build.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | springVersion = '4.2.2.RELEASE' 3 | } 4 | dependencies { 5 | compile(project(':')) { 6 | transitive = true 7 | } 8 | compile "org.springframework:spring-core:${springVersion}" 9 | compile "org.springframework:spring-context:${springVersion}" 10 | testCompile "junit:junit:${junitVersion}" 11 | testCompile "org.springframework:spring-test:${springVersion}" 12 | testCompile "org.mockito:mockito-all:${mockitoVersion}" 13 | } 14 | 15 | 16 | publishing { 17 | publications { 18 | schemagenGraphQLSpring(MavenPublication) { 19 | version version 20 | from components.java 21 | 22 | artifact sourcesJar { 23 | classifier "sources" 24 | } 25 | artifact javadocJar { 26 | classifier "javadoc" 27 | } 28 | pom.withXml { 29 | asNode().children().last() + { 30 | resolveStrategy = Closure.DELEGATE_FIRST 31 | name project.name 32 | description project.description 33 | 34 | url "https://github.com/bpatters/schemagen-graphql" 35 | scm { 36 | url "https://github.com/bpatters/schemagen-graphql" 37 | connection "https://github.com/bpatters/schemagen-graphql" 38 | developerConnection "https://github.com/bpatters/schemagen-graphql" 39 | } 40 | licenses { 41 | license { 42 | name 'MIT' 43 | url 'https://github.com/bpatters/schemagen-graphql/blob/master/LICENSE.md' 44 | distribution 'repo' 45 | } 46 | } 47 | developers { 48 | developer { 49 | id 'bpatters' 50 | name 'Bret Patterson' 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | bintray { 60 | publications = ['schemagenGraphQLSpring'] 61 | publish = true 62 | pkg { 63 | repo = 'schemagen-graphql' 64 | name = 'schemagen-graphql-spring' 65 | desc = 'Spring Datafetchers for GraphQL' 66 | licenses = ['MIT'] 67 | vcsUrl = 'https://github.com/bpatters/schemagen-graphql' 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /schemagen-graphql-spring/src/main/java/com/bretpatterson/schemagen/graphql/GraphQLSpringSchemaBuilder.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 4 | import com.bretpatterson.schemagen.graphql.datafetchers.spring.SpringDataFetcherFactory; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import com.google.common.collect.ImmutableList; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.config.BeanDefinition; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; 12 | import org.springframework.core.type.filter.AnnotationTypeFilter; 13 | import org.springframework.util.ClassUtils; 14 | 15 | import java.util.List; 16 | 17 | public class GraphQLSpringSchemaBuilder extends GraphQLSchemaBuilder { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(GraphQLSpringSchemaBuilder.class); 20 | 21 | private final ApplicationContext applicationContext; 22 | 23 | public GraphQLSpringSchemaBuilder(ApplicationContext applicationContext) { 24 | super(); 25 | 26 | this.applicationContext = applicationContext; 27 | this.registerDataFetcherFactory(new SpringDataFetcherFactory(applicationContext)); 28 | this.registerTypeMappers(getDefaultTypeMappers()); 29 | } 30 | 31 | public static List getDefaultTypeMappers() { 32 | ImmutableList.Builder builder = ImmutableList.builder(); 33 | 34 | ClassPathScanningCandidateComponentProvider scanner = 35 | new ClassPathScanningCandidateComponentProvider(true); 36 | 37 | scanner.addIncludeFilter(new AnnotationTypeFilter(GraphQLTypeMapper.class)); 38 | 39 | for (BeanDefinition bd : scanner.findCandidateComponents(IGraphQLTypeMapper.class.getPackage().getName())) { 40 | try { 41 | Class cls = ClassUtils.resolveClassName(bd.getBeanClassName(), 42 | ClassUtils.getDefaultClassLoader()); 43 | 44 | builder.add((IGraphQLTypeMapper) cls.newInstance()); 45 | } catch (Exception e) { 46 | LOGGER.error("Unexpected exception.", e); 47 | } 48 | } 49 | return builder.build(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /schemagen-graphql-spring/src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLSpringELDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import com.bretpatterson.schemagen.graphql.datafetchers.spring.SpringDataFetcher; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Fields annotated with this use the specified data fetcher for retrieval 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.FIELD, ElementType.METHOD}) 15 | public @interface GraphQLSpringELDataFetcher { 16 | 17 | /** 18 | * A custom datafetcher you would like to use for this field 19 | * @return 20 | */ 21 | Class dataFetcher() default SpringDataFetcher.class; 22 | 23 | /** 24 | * Spring EL expression that is executed as part of this datafetcher 25 | * @return 26 | */ 27 | String value(); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /schemagen-graphql-spring/src/main/java/com/bretpatterson/schemagen/graphql/datafetchers/spring/SpringDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.datafetchers.spring; 2 | 3 | import com.bretpatterson.schemagen.graphql.datafetchers.DefaultMethodDataFetcher; 4 | import com.google.common.base.Throwables; 5 | import graphql.schema.DataFetchingEnvironment; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.expression.BeanFactoryResolver; 10 | import org.springframework.expression.EvaluationContext; 11 | import org.springframework.expression.ExpressionParser; 12 | import org.springframework.expression.spel.standard.SpelExpressionParser; 13 | import org.springframework.expression.spel.support.StandardEvaluationContext; 14 | 15 | import java.lang.reflect.Method; 16 | 17 | /** 18 | * 19 | */ 20 | public class SpringDataFetcher extends DefaultMethodDataFetcher { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMethodDataFetcher.class); 23 | private String expression; 24 | private ApplicationContext context; 25 | ExpressionParser parser = new SpelExpressionParser(); 26 | BeanFactoryResolver beanFactoryResolver; 27 | 28 | private EvaluationContext getEvaluationContext(Object rootObject, Object[] arguments) { 29 | StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext(); 30 | standardEvaluationContext.setBeanResolver(beanFactoryResolver); 31 | 32 | // add the parameter values as variables 33 | int index = 0; 34 | for (String name : argumentTypeMap.keySet()) { 35 | standardEvaluationContext.setVariable(name, arguments[index++]); 36 | } 37 | standardEvaluationContext.setRootObject(rootObject); 38 | 39 | return standardEvaluationContext; 40 | } 41 | 42 | @Override 43 | public Object invokeMethod(DataFetchingEnvironment environment, Method method, Object targetObject, Object[] arguments) { 44 | try { 45 | // here we execute the spring expression 46 | return parser.parseExpression(expression).getValue(getEvaluationContext(environment.getSource(), arguments)); 47 | } catch (Exception ex) { 48 | LOGGER.error("Unexpected exception.", ex); 49 | throw Throwables.propagate(ex); 50 | } 51 | } 52 | 53 | public SpringDataFetcher setExpression(String expression) { 54 | this.expression = expression; 55 | 56 | return this; 57 | } 58 | 59 | public SpringDataFetcher setApplicationContext(ApplicationContext context) { 60 | this.context = context; 61 | // setup the bean factory resolver 62 | beanFactoryResolver = new BeanFactoryResolver(context); 63 | 64 | return this; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /schemagen-graphql-spring/src/main/java/com/bretpatterson/schemagen/graphql/datafetchers/spring/SpringDataFetcherFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.datafetchers.spring; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLSpringELDataFetcher; 5 | import com.bretpatterson.schemagen.graphql.impl.DefaultDataFetcherFactory; 6 | import com.google.common.base.Optional; 7 | import com.google.common.base.Throwables; 8 | import graphql.schema.DataFetcher; 9 | import org.springframework.beans.factory.access.el.SpringBeanELResolver; 10 | import org.springframework.context.ApplicationContext; 11 | 12 | import java.lang.reflect.Field; 13 | import java.lang.reflect.Method; 14 | 15 | import static com.google.common.base.Preconditions.checkNotNull; 16 | 17 | /** 18 | * Supports methods/fields annotated with the @GraphQLSpringDataFetcher annotation. This allows you to execute Spring EL expressions as part 19 | * of your datafetching environment. 20 | */ 21 | public class SpringDataFetcherFactory extends DefaultDataFetcherFactory { 22 | 23 | ApplicationContext context; 24 | SpringBeanELResolver springBeanELResolver; 25 | 26 | public SpringDataFetcherFactory(ApplicationContext context) { 27 | this.context = context; 28 | } 29 | 30 | @Override 31 | public DataFetcher newFieldDataFetcher(final IGraphQLObjectMapper graphQLObjectMapper, final Optional targetObject, final Field field, final String fieldName, Class dataFetcher) { 32 | 33 | SpringDataFetcher dataFetcherObject = null; 34 | try { 35 | GraphQLSpringELDataFetcher springDataFetcher = field.getAnnotation(GraphQLSpringELDataFetcher.class); 36 | if (springDataFetcher != null) { 37 | dataFetcherObject = springDataFetcher.dataFetcher().newInstance(); 38 | dataFetcherObject.setFieldName(field.getName()); 39 | dataFetcherObject.setTypeFactory(graphQLObjectMapper.getTypeFactory()); 40 | dataFetcherObject.setTargetObject(null); 41 | dataFetcherObject.setMethod(null); 42 | dataFetcherObject.setExpression(springDataFetcher.value()); 43 | dataFetcherObject.setApplicationContext(context); 44 | return dataFetcherObject; 45 | } 46 | } catch (Exception ex) { 47 | throw Throwables.propagate(ex); 48 | } 49 | 50 | return super.newFieldDataFetcher(graphQLObjectMapper, targetObject, field, fieldName, dataFetcher); 51 | } 52 | 53 | @Override 54 | public DataFetcher newMethodDataFetcher(final IGraphQLObjectMapper graphQLObjectMapper, 55 | final Optional targetObject, 56 | final Method method, 57 | final String fieldName, 58 | final Class dataFetcher) { 59 | checkNotNull(method); 60 | SpringDataFetcher dataFetcherObject = null; 61 | try { 62 | GraphQLSpringELDataFetcher springDataFetcher = method.getAnnotation(GraphQLSpringELDataFetcher.class); 63 | if (springDataFetcher != null) { 64 | dataFetcherObject = springDataFetcher.dataFetcher().newInstance(); 65 | dataFetcherObject.setFieldName(fieldName); 66 | dataFetcherObject.setTypeFactory(graphQLObjectMapper.getTypeFactory()); 67 | dataFetcherObject.setTargetObject(targetObject); 68 | dataFetcherObject.setMethod(method); 69 | dataFetcherObject.setExpression(springDataFetcher.value()); 70 | dataFetcherObject.setApplicationContext(context); 71 | return dataFetcherObject; 72 | } 73 | } catch (Exception ex) { 74 | throw Throwables.propagate(ex); 75 | } 76 | return super.newMethodDataFetcher(graphQLObjectMapper, targetObject, method, fieldName, dataFetcher); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /schemagen-graphql-spring/src/test/java/com/bretpatterson/schemagen/graphq/datafetchers/spring/EchoBean.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphq.datafetchers.spring; 2 | 3 | /** 4 | * Created by bpatterson on 3/1/16. 5 | */ 6 | public class EchoBean { 7 | public String echo(Object... params) { 8 | StringBuilder sb = new StringBuilder(); 9 | for (Object param : params) { 10 | sb.append(":"); 11 | sb.append(param.toString()); 12 | } 13 | 14 | return sb.toString(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /schemagen-graphql-spring/src/test/java/com/bretpatterson/schemagen/graphq/datafetchers/spring/SpringDataFetcherFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphq.datafetchers.spring; 2 | 3 | import com.bretpatterson.schemagen.graphql.GraphQLSchemaBuilder; 4 | import com.bretpatterson.schemagen.graphql.IDataFetcherFactory; 5 | import com.bretpatterson.schemagen.graphql.ITypeNamingStrategy; 6 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLParam; 7 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLSpringELDataFetcher; 8 | import com.bretpatterson.schemagen.graphql.datafetchers.IDataFetcher; 9 | import com.bretpatterson.schemagen.graphql.datafetchers.spring.SpringDataFetcherFactory; 10 | import com.bretpatterson.schemagen.graphql.impl.GraphQLObjectMapper; 11 | import com.bretpatterson.schemagen.graphql.impl.SimpleTypeFactory; 12 | import com.google.common.base.Optional; 13 | import com.google.common.collect.ImmutableList; 14 | import graphql.language.Field; 15 | import graphql.schema.DataFetchingEnvironment; 16 | import graphql.schema.GraphQLFieldDefinition; 17 | import org.junit.Test; 18 | import org.junit.runner.RunWith; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.context.ApplicationContext; 21 | import org.springframework.test.context.ContextConfiguration; 22 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 23 | 24 | import java.lang.reflect.Method; 25 | import java.util.Collection; 26 | import java.util.List; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | import static org.junit.Assert.fail; 30 | import static org.mockito.BDDMockito.willReturn; 31 | import static org.mockito.Mockito.mock; 32 | 33 | /** 34 | * Created by bpatterson on 3/1/16. 35 | */ 36 | @RunWith(SpringJUnit4ClassRunner.class) 37 | @ContextConfiguration(classes = {TestModule.class}) 38 | public class SpringDataFetcherFactoryTest { 39 | @Autowired 40 | ApplicationContext context; 41 | 42 | 43 | public class SpringDataFetcherTest { 44 | @GraphQLSpringELDataFetcher(value="@echoBean.echo('Hello World!')") 45 | String fieldTest = "Invalid Value!"; 46 | 47 | @GraphQLSpringELDataFetcher("@echoBean.echo(#param1, #param2)") 48 | public String getData(@GraphQLParam(name = "param1") String param1, @GraphQLParam(name = "param2") Integer param2) { 49 | return "data value"; 50 | } 51 | 52 | public String getDataNoSpring(@GraphQLParam(name = "param1") String param1, @GraphQLParam(name = "param2") Integer param2) { 53 | return "data value"; 54 | } 55 | } 56 | 57 | @Test 58 | public void testDefaultDataFetcher() throws NoSuchMethodException { 59 | SpringDataFetcherFactory factory = new SpringDataFetcherFactory(context); 60 | GraphQLObjectMapper graphQLObjectMapper = new GraphQLObjectMapper(new SimpleTypeFactory(), 61 | GraphQLSchemaBuilder.getDefaultTypeMappers(), 62 | Optional.absent(), 63 | Optional. of(factory), 64 | Optional.> absent(), 65 | GraphQLSchemaBuilder.getDefaultTypeConverters(), 66 | ImmutableList.> of()); 67 | 68 | Collection fieldDefinitions = graphQLObjectMapper.getGraphQLFieldDefinitions(Optional.of(new SpringDataFetcherTest()), SpringDataFetcherTest.class, SpringDataFetcherTest.class, 69 | Optional.>absent(), 70 | Optional.>absent()); 71 | 72 | DataFetchingEnvironment environment = mock(DataFetchingEnvironment.class); 73 | Field field = mock(Field.class); 74 | 75 | willReturn(this).given(environment).getSource(); 76 | willReturn(ImmutableList.of(field)).given(environment).getFields(); 77 | willReturn("value1").given(environment).getArgument("param1"); 78 | willReturn(1234).given(environment).getArgument("param2"); 79 | 80 | for (GraphQLFieldDefinition graphQLFieldDefinition : fieldDefinitions) { 81 | if (graphQLFieldDefinition.getName().equals("data")) { 82 | willReturn("data").given(field).getName(); 83 | String value = (String)graphQLFieldDefinition.getDataFetcher().get(environment); 84 | assertEquals(":value1:1234", value); 85 | } else if (graphQLFieldDefinition.getName().equals("dataNoSpring")) { 86 | willReturn("dataNoSpring").given(field).getName(); 87 | String value = (String)graphQLFieldDefinition.getDataFetcher().get(environment); 88 | assertEquals("data value", value); 89 | } else if (graphQLFieldDefinition.getName().equals("fieldTest")) { 90 | willReturn("fieldTest").given(field).getName(); 91 | String value = (String)graphQLFieldDefinition.getDataFetcher().get(environment); 92 | assertEquals(":Hello World!", value); 93 | } else { 94 | fail("fields not named properly"); 95 | } 96 | } 97 | } 98 | 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /schemagen-graphql-spring/src/test/java/com/bretpatterson/schemagen/graphq/datafetchers/spring/TestModule.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphq.datafetchers.spring; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | /** 7 | * Created by bpatterson on 3/1/16. 8 | */ 9 | @Configuration 10 | public class TestModule { 11 | 12 | @Bean(name="echoBean") 13 | public EchoBean getEchoBean() { 14 | return new EchoBean(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'schemagen-graphql' 2 | include 'schemagen-graphql-joda' 3 | include 'schemagen-graphql-guava' 4 | include 'schemagen-graphql-examples' 5 | include 'schemagen-graphql-spring' 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/IDataFetcherFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLDataFetcher; 4 | import com.google.common.base.Optional; 5 | import graphql.schema.DataFetcher; 6 | 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * Interface that allows you to control the instantiation of all custom DataFetchers. This allows you to initialize 12 | * the datafetchers with any necessary contextual information. 13 | */ 14 | public interface IDataFetcherFactory { 15 | 16 | /** 17 | * Factory for creating data fetchers for Field definitions annotated with {@link GraphQLDataFetcher}. 18 | * @param objectMapper the {@link IGraphQLObjectMapper} 19 | * @param field the Field object itself 20 | * @param dataFetcher 21 | * @return 22 | */ 23 | DataFetcher newFieldDataFetcher(IGraphQLObjectMapper objectMapper, Optional targetObject, Field field, String fieldName, Class dataFetcher); 24 | 25 | /** 26 | * Factory for creating data fetchers for Method definitions annotated with {@link GraphQLDataFetcher}. 27 | * 28 | * @param objectMapper 29 | * @param targetObject 30 | * @param method 31 | * @param fieldName 32 | * @param dataFetcher 33 | * @return 34 | */ 35 | DataFetcher newMethodDataFetcher(IGraphQLObjectMapper objectMapper, Optional targetObject, Method method, String fieldName, Class dataFetcher); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/IGraphQLObjectMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | 4 | import com.bretpatterson.schemagen.graphql.datafetchers.IDataFetcher; 5 | import com.google.common.base.Optional; 6 | import graphql.schema.GraphQLFieldDefinition; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | import graphql.schema.GraphQLType; 10 | 11 | import java.lang.reflect.Field; 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Type; 14 | import java.util.Collection; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | /** 19 | * A GraphQLObjectMapper knows how to build a GraphQLDefinition for objects. 20 | */ 21 | public interface IGraphQLObjectMapper { 22 | 23 | /** 24 | * Get the object responsible for naming GraphQL types. 25 | * @return 26 | */ 27 | ITypeNamingStrategy getTypeNamingStrategy(); 28 | 29 | /** 30 | * Get an input definition for the specified type. 31 | * @param type 32 | */ 33 | GraphQLInputType getInputType(Type type); 34 | 35 | /** 36 | * Get an output definition for the specified type. 37 | * @param type 38 | */ 39 | GraphQLOutputType getOutputType(Type type); 40 | 41 | /** 42 | * Get the Input Type cache. This is useful for custom type mappers who 43 | * might need to add TypeReference's to the cache when they need to process 44 | * an object that contains a two way dependency between itself and another object. 45 | */ 46 | IGraphQLTypeCache getInputTypeCache(); 47 | 48 | /** 49 | * Get the Input Type cache. This is useful for custom type mappers who 50 | * might need to add TypeReference's to the cache when they need to process 51 | * an object that contains a two way dependency between itself and another object. 52 | */ 53 | IGraphQLTypeCache getOutputTypeCache(); 54 | 55 | /** 56 | * Returns the object responsible for object type conversion. 57 | * @return 58 | */ 59 | ITypeFactory getTypeFactory(); 60 | 61 | /** 62 | * Get the raw type of this object for generic types 63 | * @param type 64 | * @return 65 | */ 66 | Class getClassFromType(Type type); 67 | 68 | /** 69 | * Returns all input types created. 70 | * @return 71 | */ 72 | Set getInputTypes(); 73 | 74 | /** 75 | * Get the datafetcher factory 76 | * @return 77 | */ 78 | IDataFetcherFactory getDataFetcherFactory() ; 79 | 80 | /** 81 | * 82 | * @param dataFetcherFactory 83 | */ 84 | void setDataFetcherFactory(IDataFetcherFactory dataFetcherFactory); 85 | 86 | /** 87 | * Get the default datafetcher for methods 88 | * @return 89 | */ 90 | Class getDefaultMethodDataFetcher(); 91 | 92 | /** 93 | * Set the default data fetcher used for methods 94 | * @param defaultMethodDataFetcher 95 | */ 96 | void setDefaultMethodDataFetcher(Class defaultMethodDataFetcher); 97 | 98 | Collection getGraphQLFieldDefinitions(Optional targetObject, Type type, Class classItem, Optional> fields, Optional> methods); 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/IGraphQLTypeCache.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | /** 4 | * A simple abstraction around a key/value cache 5 | */ 6 | public interface IGraphQLTypeCache { 7 | 8 | boolean containsKey(String key); 9 | 10 | T put(String typeName, T value); 11 | 12 | T get(String typeName); 13 | 14 | T remove(String typeName); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/IMutationFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | import graphql.schema.GraphQLFieldDefinition; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * An interface for a Factory that knows how to convert an instance object into a List of mutations. 9 | * {@link com.bretpatterson.schemagen.graphql.impl.DefaultQueryAndMutationFactory} 10 | */ 11 | public interface IMutationFactory { 12 | 13 | List newMethodMutationsForObject(IGraphQLObjectMapper graphQLObjectMapper, Object sourceObject); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/IQueryFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | import com.bretpatterson.schemagen.graphql.impl.DefaultQueryAndMutationFactory; 4 | import graphql.schema.GraphQLFieldDefinition; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * An interface for a Factory that knows how to convert an instance object into a List of queries. 10 | * {@link DefaultQueryAndMutationFactory} 11 | */ 12 | public interface IQueryFactory { 13 | 14 | List newMethodQueriesForObject(IGraphQLObjectMapper graphQLObjectMapper, Object sourceObject); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/ITypeFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | import java.lang.reflect.Type; 4 | 5 | /** 6 | * Created by bpatterson on 1/22/16. 7 | */ 8 | public interface ITypeFactory { 9 | /** 10 | * This object needs to know how to convert objects to the specified type. This is used 11 | * to convert query Parameters to the correct type before method invocation. 12 | * Object type of the arg can be any object type within java we exposed through GraphQL as a parameter. 13 | * IE everyone of your query/mutation parameter objects must be handled by this object. 14 | * Jackson object mapper is a simple way of doing this. IE: 15 | *
16 | 	 * {@code
17 | 	 * {@liter @Override}
18 | 	 * public Object convertToType(Type type, Object arg) {
19 | 	 *		try {
20 | 	 *			return objectMapper.readValue(objectMapper.writeValueAsString(arg), TypeFactory.defaultInstance().constructType(type));
21 | 	 *		} catch(IOException ex) {
22 | 	 *			return Throwables.propagate(ex);
23 | 	 *		}
24 | 	 *
25 | 	 *	}
26 | 	 * }
27 | * 28 | * @param type The type to convert the object to. This is a ParameterizedType if the original value is so you can recursively determine 29 | * what value you should convert to. IE: {@code List>} 30 | * @param arg The GraphQL version of the argument value IE: Primitive, Object, List of ... 31 | * @return 32 | */ 33 | Object convertToType(Type type, Object arg); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/ITypeNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | import java.lang.reflect.Type; 4 | 5 | /** 6 | * Interface for implementing custom type naming strategies for schema generation. 7 | */ 8 | public interface ITypeNamingStrategy { 9 | 10 | /** 11 | * Get the GraphQL type name for the specified type 12 | * @param graphQLObjectMapper 13 | * @param type 14 | * @return 15 | */ 16 | String getTypeName(IGraphQLObjectMapper graphQLObjectMapper, Type type); 17 | 18 | /** 19 | * String to append to GraphQL InputType's 20 | * @return 21 | */ 22 | String getInputTypePostfix(); 23 | 24 | /** 25 | * Delimiter used for separating sections of a type name 26 | * @return 27 | */ 28 | String getDelimiter(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/Introspection.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | /** 4 | * Static set of tools for schema generation and introspection. 5 | */ 6 | public class Introspection { 7 | 8 | // The introspection query to end all introspection queries, copied from 9 | // https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js 10 | public static String INTROSPECTION_QUERY = "query IntrospectionQuery {\n" + 11 | " __schema {\n" + 12 | " queryType { name }\n" + 13 | " mutationType { name }\n" + 14 | // " subscriptionType { name }\n" + 15 | " types {\n" + 16 | " ...FullType\n" + 17 | " }\n" + 18 | " directives {\n" + 19 | " name\n" + 20 | " description\n" + 21 | " args {\n" + 22 | " ...InputValue\n" + 23 | " }\n" + 24 | " onOperation\n" + 25 | " onFragment\n" + 26 | " onField\n" + 27 | " }\n" + 28 | " }\n" + 29 | "}\n" + 30 | "fragment FullType on __Type {\n" + 31 | " kind\n" + 32 | " name\n" + 33 | " description\n" + 34 | " fields(includeDeprecated: true) {\n" + 35 | " name\n" + 36 | " description\n" + 37 | " args {\n" + 38 | " ...InputValue\n" + 39 | " }\n" + 40 | " type {\n" + 41 | " ...TypeRef\n" + 42 | " }\n" + 43 | " isDeprecated\n" + 44 | " deprecationReason\n" + 45 | " }\n" + 46 | " inputFields {\n" + 47 | " ...InputValue\n" + 48 | " }\n" + 49 | " interfaces {\n" + 50 | " ...TypeRef\n" + 51 | " }\n" + 52 | " enumValues(includeDeprecated: true) {\n" + 53 | " name\n" + 54 | " description\n" + 55 | " isDeprecated\n" + 56 | " deprecationReason\n" + 57 | " }\n" + 58 | " possibleTypes {\n" + 59 | " ...TypeRef\n" + 60 | " }\n" + 61 | "}\n" + 62 | "fragment InputValue on __InputValue {\n" + 63 | " name\n" + 64 | " description\n" + 65 | " type { ...TypeRef }\n" + 66 | " defaultValue\n" + 67 | "}\n" + 68 | "fragment TypeRef on __Type {\n" + 69 | " kind\n" + 70 | " name\n" + 71 | " ofType {\n" + 72 | " kind\n" + 73 | " name\n" + 74 | " ofType {\n" + 75 | " kind\n" + 76 | " name\n" + 77 | " ofType {\n" + 78 | " kind\n" + 79 | " name\n" + 80 | " }\n" + 81 | " }\n" + 82 | " }\n" + 83 | "}\n"; 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLController.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import com.bretpatterson.schemagen.graphql.IMutationFactory; 4 | import com.bretpatterson.schemagen.graphql.IQueryFactory; 5 | import com.bretpatterson.schemagen.graphql.impl.DefaultQueryAndMutationFactory; 6 | import com.bretpatterson.schemagen.graphql.utils.AnnotationUtils; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * Classes annotated with this method are scanned for methods defined as queryable. 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.TYPE}) 18 | public @interface GraphQLController { 19 | 20 | 21 | /** 22 | * When using relay this must be set, otherwise it's an optional object name to wrapper 23 | * this controllers top level queries within. 24 | * @return 25 | */ 26 | String rootQueriesObjectName() default AnnotationUtils.DEFAULT_NULL; 27 | 28 | /** 29 | * When using relay this must be set, otherwise it's an optional object name to wrapper 30 | * this controllers top level mutations within. 31 | * @return 32 | */ 33 | String rootMutationsObjectName() default AnnotationUtils.DEFAULT_NULL; 34 | 35 | /** 36 | * 37 | * This factory that will be used to generate queries for this object, if any. 38 | * Default factory scans the object for {@link GraphQLQuery} annotated methods and turns 39 | * the methods into queries. 40 | * @return 41 | */ 42 | Class queryFactory() default DefaultQueryAndMutationFactory.class; 43 | 44 | /** 45 | * This factory that will be used to generate mutations for this object, if any. 46 | * Default factory scans the object for {@link GraphQLMutation} annotated methods and turns 47 | * the methods into mutations. 48 | * @return 49 | */ 50 | Class mutationFactory() default DefaultQueryAndMutationFactory.class; 51 | 52 | /** 53 | * Set the description for the root query object name 54 | * @return 55 | */ 56 | String queryDescription() default AnnotationUtils.DEFAULT_NULL; 57 | 58 | /** 59 | * Set the description for the root mutation object name 60 | * @return 61 | */ 62 | String mutationDescription() default AnnotationUtils.DEFAULT_NULL; 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | 4 | import graphql.schema.DataFetcher; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * Fields annotated with this use the specified data fetcher for retrieval 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ElementType.FIELD, ElementType.METHOD}) 16 | public @interface GraphQLDataFetcher { 17 | 18 | /** 19 | * A custom datafetcher you would like to use for this field 20 | * @return 21 | */ 22 | Class dataFetcher(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLDeprecated.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Fields annotated with this method will be ignored 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.FIELD, ElementType.METHOD}) 13 | public @interface GraphQLDeprecated { 14 | String value(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLDescription.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Used to set the description of a Field. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) 13 | public @interface GraphQLDescription { 14 | 15 | /** 16 | * The description of the field 17 | * @return 18 | */ 19 | String value(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLIgnore.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Fields annotated with this method will be ignored 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.FIELD, ElementType.METHOD}) 13 | public @interface GraphQLIgnore { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLMutation.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import com.bretpatterson.schemagen.graphql.datafetchers.DefaultMethodDataFetcher; 4 | import com.bretpatterson.schemagen.graphql.utils.AnnotationUtils; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | 12 | /** 13 | * Methods annotated with this method will be processed as field mutations and can be 14 | * configured with the specified datafetcher or {@link DefaultMethodDataFetcher} by default. 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.METHOD}) 18 | public @interface GraphQLMutation { 19 | /** 20 | * The field name for this mutation. 21 | * @return 22 | */ 23 | String name() default AnnotationUtils.DEFAULT_NULL; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLName.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Used to set the name for an Object or Field. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) 13 | public @interface GraphQLName { 14 | 15 | /** 16 | * the name of the object or field 17 | * @return 18 | */ 19 | String name(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLParam.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import com.bretpatterson.schemagen.graphql.utils.AnnotationUtils; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Created by bpatterson on 1/18/16. 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.PARAMETER}) 15 | public @interface GraphQLParam { 16 | /** 17 | * The Query/Mutation parameter name 18 | * @return 19 | */ 20 | String name(); 21 | 22 | /** 23 | * The Default value for this property. Defaults to null. 24 | * @return 25 | */ 26 | String defaultValue() default AnnotationUtils.DEFAULT_NULL; 27 | 28 | /** 29 | * Set to true if this property is required. Defaults to false 30 | * @return 31 | */ 32 | boolean required() default false; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLQuery.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import com.bretpatterson.schemagen.graphql.datafetchers.DefaultMethodDataFetcher; 4 | import com.bretpatterson.schemagen.graphql.utils.AnnotationUtils; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | 11 | /** 12 | * Methods annotated with this method will be processed as field queries and be 13 | * configured with the specified datafetcher or {@link DefaultMethodDataFetcher} by default. 14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target({ElementType.METHOD}) 17 | public @interface GraphQLQuery { 18 | 19 | /** 20 | * This name of the field exposed as a query. 21 | * @return 22 | */ 23 | String name() default AnnotationUtils.DEFAULT_NULL; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLRequired.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Used to indicate that a field of an input object is required 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.FIELD}) 13 | public @interface GraphQLRequired { 14 | 15 | /** 16 | * The name of the object or field 17 | * @return 18 | */ 19 | boolean value(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLTypeConverter.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import com.bretpatterson.schemagen.graphql.datafetchers.DefaultTypeConverter; 10 | 11 | /** 12 | * Fields annotated with this use the specified type converter for type conversion 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ElementType.FIELD, ElementType.METHOD}) 16 | public @interface GraphQLTypeConverter { 17 | 18 | /** 19 | * A custom type converter you would like to use for this field. 20 | * @return 21 | */ 22 | Class typeConverter(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/annotations/GraphQLTypeMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.annotations; 2 | 3 | import com.bretpatterson.schemagen.graphql.utils.AnnotationUtils; 4 | import graphql.schema.DataFetcher; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * Annotation to use to configure a default type mapper for a type 14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target({ElementType.TYPE}) 17 | public @interface GraphQLTypeMapper { 18 | 19 | /** 20 | * The Class this type mapper knows how to handle. 21 | * @return 22 | */ 23 | Class type(); 24 | 25 | 26 | /** 27 | * Allows you to override the default datafetcher for this data type. 28 | * @return 29 | */ 30 | Class dataFetcher() default AnnotationUtils.DEFAULT_NULL_CLASS.class; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/datafetchers/CollectionConverterDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.datafetchers; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import graphql.schema.DataFetcher; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * This converts any Collection object to a ImmutableList of the objects. Additionally this converts a null value to an empty list. 11 | */ 12 | public class CollectionConverterDataFetcher extends DefaultTypeConverter { 13 | 14 | public CollectionConverterDataFetcher(DataFetcher dataFetcher) { 15 | super(dataFetcher); 16 | } 17 | 18 | @Override 19 | public Object convert(Object value) { 20 | Collection rv = (Collection) value; 21 | 22 | if (rv == null) { 23 | return ImmutableList.of(); 24 | } 25 | 26 | return ImmutableList.copyOf(rv); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/datafetchers/DefaultMethodDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.datafetchers; 2 | 3 | import com.bretpatterson.schemagen.graphql.ITypeFactory; 4 | import com.google.common.base.Optional; 5 | import com.google.common.base.Throwables; 6 | import com.google.common.collect.Maps; 7 | import graphql.language.Field; 8 | import graphql.schema.DataFetchingEnvironment; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Type; 14 | import java.util.LinkedHashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * Implementation of a IMethodDataFetcher that will invoke a method call with the provided GraphQL arguments. If null and a default value is 19 | * provided for the argument then the default value will be used, otherwise null will be sent to the method. Parameter objects are converted 20 | * from GraphQL Deserialized types to the arguments declared type using the regisered {@link ITypeFactory}. 21 | */ 22 | public class DefaultMethodDataFetcher implements IMethodDataFetcher { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMethodDataFetcher.class); 25 | protected ITypeFactory typeFactory; 26 | 27 | protected Method method; 28 | protected String fieldName; 29 | protected Optional targetObject = Optional.absent(); 30 | protected LinkedHashMap argumentTypeMap = new LinkedHashMap<>(); 31 | protected Map parameterDefaultValue = Maps.newHashMap(); 32 | 33 | @Override 34 | public void setTargetObject(Object targetObject) { 35 | this.targetObject = Optional.fromNullable(targetObject); 36 | } 37 | 38 | @Override 39 | public void addParam(String name, Type type, Optional defaultValue) { 40 | argumentTypeMap.put(name, type); 41 | if (defaultValue.isPresent()) { 42 | parameterDefaultValue.put(name, defaultValue.get()); 43 | } 44 | } 45 | 46 | Object getDefaultValue(DataFetchingEnvironment environment, String name, Type argumentType) { 47 | if (parameterDefaultValue.containsKey(name)) { 48 | return typeFactory.convertToType(argumentTypeMap.get(name), parameterDefaultValue.get(name)); 49 | } else { 50 | return null; 51 | } 52 | } 53 | 54 | Object convertToType(Type argumentType, Object value) { 55 | if (value == null) 56 | return value; 57 | 58 | return typeFactory.convertToType(argumentType, value); 59 | } 60 | 61 | Object getParamValue(DataFetchingEnvironment environment, String argumentName, Type argumentType) { 62 | Object value = environment.getArgument(argumentName); 63 | if (value == null) { 64 | value = getDefaultValue(environment, argumentName, argumentType); 65 | } 66 | return convertToType(argumentType, value); 67 | } 68 | 69 | @Override 70 | public Object get(DataFetchingEnvironment environment) { 71 | 72 | for (Field field : environment.getFields()) { 73 | if (field.getName().equals(fieldName)) { 74 | Object[] arguments = new Object[argumentTypeMap.size()]; 75 | int index = 0; 76 | for (String argumentName : argumentTypeMap.keySet()) { 77 | arguments[index] = getParamValue(environment, argumentName, argumentTypeMap.get(argumentName)); 78 | index++; 79 | } 80 | return invokeMethod(environment, method, targetObject.isPresent() ? targetObject.get() : environment.getSource(), arguments); 81 | } 82 | } 83 | return null; 84 | 85 | } 86 | 87 | @Override 88 | public Object invokeMethod(DataFetchingEnvironment environment, Method method, Object target, Object[] arguments) { 89 | try { 90 | return method.invoke(target, (Object[]) arguments); 91 | } catch (Exception ex) { 92 | LOGGER.error("Unexpected exception.", ex); 93 | throw Throwables.propagate(ex); 94 | } 95 | } 96 | 97 | @Override 98 | public void setFieldName(String fieldName) { 99 | this.fieldName = fieldName; 100 | } 101 | 102 | @Override 103 | public void setMethod(Method method) { 104 | this.method = method; 105 | } 106 | 107 | @Override 108 | public void setTypeFactory(ITypeFactory typeFactory) { 109 | this.typeFactory = typeFactory; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/datafetchers/DefaultTypeConverter.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.datafetchers; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | 6 | /** 7 | * Created by bpatterson on 3/3/16. 8 | */ 9 | public abstract class DefaultTypeConverter implements DataFetcher { 10 | private DataFetcher dataFetcher; 11 | 12 | public DefaultTypeConverter(DataFetcher dataFetcher) { 13 | this.dataFetcher = dataFetcher; 14 | } 15 | 16 | @Override 17 | public Object get(DataFetchingEnvironment environment) { 18 | return convert(dataFetcher.get(environment)); 19 | } 20 | 21 | public Object convert(Object rv) { 22 | return rv; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/datafetchers/IDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.datafetchers; 2 | 3 | import com.google.common.base.Optional; 4 | import graphql.schema.DataFetcher; 5 | 6 | import java.lang.reflect.Type; 7 | 8 | /** 9 | * Created by bpatterson on 2/19/16. 10 | */ 11 | public interface IDataFetcher extends DataFetcher { 12 | 13 | /** 14 | * When generating a GraphQL Field definition we make multiple call 15 | * to this method to add it's parameters. The invocation order will be the exact order 16 | * of the parameters. 17 | * @param name the parameter name 18 | * @param type the generic type of the parameter 19 | * @param defaultValue optionally the default value defined 20 | */ 21 | void addParam(String name, Type type, Optional defaultValue); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/datafetchers/IMethodDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.datafetchers; 2 | 3 | import com.bretpatterson.schemagen.graphql.ITypeFactory; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLQuery; 5 | import graphql.schema.DataFetchingEnvironment; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * Implementation of a DataFetcher that contains knowledge of how to call into a method. 11 | * This includes the Type of all parameters, the Target Object, the field Name and 12 | * a reference to the ITypeFactory for converting from Generic GraphQL deserialized Types 13 | * into the parameter types of the method. 14 | */ 15 | public interface IMethodDataFetcher extends IDataFetcher { 16 | 17 | /** 18 | * Called to let the data fetcher know about the Type factory registered for 19 | * converting GraphQL deserialized data types to your Application method specific data types. 20 | * {@link ITypeFactory} 21 | * @param typeFactory 22 | */ 23 | void setTypeFactory(ITypeFactory typeFactory); 24 | 25 | /** 26 | * The field name that is used to invoke the query.{@link GraphQLQuery#name()} 27 | * 28 | * @param fieldName 29 | */ 30 | void setFieldName(String fieldName); 31 | 32 | /** 33 | * A reference to the method we will be invoking for this data fetcher. 34 | * @param method 35 | */ 36 | void setMethod(Method method); 37 | 38 | /** 39 | * The target object the method will be invoked upon. 40 | * @param targetObject 41 | */ 42 | void setTargetObject(Object targetObject); 43 | 44 | /** 45 | * Invokes the method on the target object with the specified arguments 46 | * @param method the method to invoke 47 | * @param targetObject the target object 48 | * @param arguments the arguments to the method 49 | * @return return value 50 | */ 51 | Object invokeMethod(DataFetchingEnvironment environment, Method method, Object targetObject, Object[] arguments); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/datafetchers/MapConverterDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.datafetchers; 2 | 3 | import java.util.Map; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | 7 | import graphql.schema.DataFetcher; 8 | 9 | /** 10 | * This converts all Maps into a List of Entries who's key/values are accessible 11 | * This gets added to all Map's by default so they can be exposed through GraphQL 12 | */ 13 | public class MapConverterDataFetcher extends DefaultTypeConverter { 14 | 15 | public MapConverterDataFetcher(DataFetcher dataFetcher) { 16 | super(dataFetcher); 17 | } 18 | 19 | @SuppressWarnings("unchecked") 20 | @Override 21 | public Object convert(Object value) { 22 | 23 | if (value == null) { 24 | return ImmutableList.of(); 25 | } 26 | Map valueMap = (Map) value; 27 | // build an accessible copy of the entries to ensure we can get them via property datafetcher 28 | ImmutableList.Builder> valueList = ImmutableList.builder(); 29 | for (final Map.Entry entry : valueMap.entrySet()) { 30 | valueList.add(new Entry(entry)); 31 | } 32 | return valueList.build(); 33 | } 34 | 35 | /** 36 | * This holds a Map.Entry instance that we use to hold the Map.Entry in maps that we have remapped to {@code List} objects. 37 | */ 38 | public class Entry implements Map.Entry { 39 | 40 | Object key; 41 | Object value; 42 | 43 | public Entry(Map.Entry entry) { 44 | this.key = entry.getKey(); 45 | this.value = entry.getValue(); 46 | } 47 | 48 | @Override 49 | public Object getKey() { 50 | return key; 51 | } 52 | 53 | @Override 54 | public Object getValue() { 55 | return value; 56 | } 57 | 58 | @Override 59 | public Object setValue(final Object value) { 60 | throw new IllegalAccessError("Not implemented"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/exceptions/NotMappableException.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.exceptions; 2 | 3 | /** 4 | * Created by bpatterson on 1/23/16. 5 | */ 6 | public class NotMappableException extends RuntimeException { 7 | private static final long serialVersionUID = -8042949483199730066L; 8 | 9 | public NotMappableException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/impl/DefaultDataFetcherFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.IDataFetcherFactory; 4 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 5 | import com.bretpatterson.schemagen.graphql.datafetchers.IMethodDataFetcher; 6 | import com.google.common.base.Optional; 7 | import com.google.common.base.Throwables; 8 | import graphql.schema.DataFetcher; 9 | import graphql.schema.PropertyDataFetcher; 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.Method; 12 | import java.security.InvalidParameterException; 13 | 14 | /** 15 | * The Default DataFetcher Factory. This only supports custom datafetchers of type IMethodDataFetcher 16 | */ 17 | public class DefaultDataFetcherFactory implements IDataFetcherFactory { 18 | 19 | @Override 20 | public DataFetcher newFieldDataFetcher(final IGraphQLObjectMapper objectMapper, final Optional targetObject, final Field field, String fieldName, Class dataFetcher) { 21 | if (dataFetcher != null) { 22 | try { 23 | return dataFetcher.newInstance(); 24 | } catch (IllegalAccessException | InstantiationException ex) { 25 | throw Throwables.propagate(ex); 26 | } 27 | } else { 28 | return new PropertyDataFetcher(fieldName); 29 | } 30 | } 31 | 32 | @Override 33 | public DataFetcher newMethodDataFetcher(final IGraphQLObjectMapper graphQLObjectMapper, 34 | final Optional targetObject, 35 | final Method method, 36 | final String fieldName, 37 | final Class dataFetcher) { 38 | DataFetcher dataFetcherObject; 39 | try { 40 | dataFetcherObject = dataFetcher.newInstance(); 41 | if (!IMethodDataFetcher.class.isAssignableFrom(dataFetcher)) { 42 | throw new InvalidParameterException("This Data Fetcher Factory only supports IMethodDataFetchers"); 43 | } 44 | IMethodDataFetcher methodDataFetcher = (IMethodDataFetcher) dataFetcherObject; 45 | methodDataFetcher.setFieldName(fieldName); 46 | methodDataFetcher.setTypeFactory(graphQLObjectMapper.getTypeFactory()); 47 | if (targetObject.isPresent()) { 48 | methodDataFetcher.setTargetObject(targetObject.get()); 49 | } 50 | methodDataFetcher.setMethod(method); 51 | } catch (Exception ex) { 52 | throw Throwables.propagate(ex); 53 | } 54 | return dataFetcherObject; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/impl/DefaultGraphQLTypeCache.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLTypeCache; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Cache of GraphQL Types we've discovered during parsing. 10 | */ 11 | public class DefaultGraphQLTypeCache implements IGraphQLTypeCache { 12 | 13 | Map nameCache = new HashMap(); 14 | 15 | @Override 16 | public boolean containsKey(String key) { 17 | return nameCache.containsKey(key); 18 | } 19 | 20 | @Override 21 | public T put(String name, T value) { 22 | nameCache.put(name, value); 23 | return value; 24 | } 25 | 26 | @Override 27 | public T get(String name) { 28 | return nameCache.get(name); 29 | } 30 | 31 | @Override 32 | public T remove(String t) { 33 | return nameCache.remove(t); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/impl/DefaultQueryAndMutationFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.IMutationFactory; 5 | import com.bretpatterson.schemagen.graphql.IQueryFactory; 6 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLMutation; 7 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLQuery; 8 | import com.bretpatterson.schemagen.graphql.utils.AnnotationUtils; 9 | import com.google.common.base.Optional; 10 | import com.google.common.collect.Lists; 11 | import graphql.schema.GraphQLFieldDefinition; 12 | import graphql.schema.GraphQLOutputType; 13 | 14 | import java.lang.reflect.Method; 15 | import java.util.List; 16 | 17 | /** 18 | * Default implementation of the query/mutation factory. Converts a Method signature on an object into a: 19 | *
    20 | *
  1. List of GraphQL field definitions
  2. 21 | *
  3. GraphQLOutput type for the return type of the method.
  4. 22 | *
  5. Configures the Datafetcher based on the Object Instance, Field Name, Method Signature, and Type Factory
  6. 23 | *
24 | */ 25 | public class DefaultQueryAndMutationFactory implements IQueryFactory, IMutationFactory { 26 | 27 | @Override 28 | public List newMethodMutationsForObject(IGraphQLObjectMapper graphQLObjectMapper, Object targetObject) { 29 | List results = Lists.newLinkedList(); 30 | 31 | results.addAll(graphQLObjectMapper.getGraphQLFieldDefinitions( 32 | Optional.of(targetObject), 33 | targetObject.getClass(), 34 | targetObject.getClass(), 35 | Optional.of(AnnotationUtils.getFieldsWithAnnotation(targetObject.getClass(), GraphQLMutation.class)), 36 | Optional.of(AnnotationUtils.getMethodsWithAnnotation(targetObject.getClass(), GraphQLMutation.class)))); 37 | 38 | return results; 39 | } 40 | 41 | /** 42 | * Generates field definitions, with arguments, from all {@link GraphQLQuery} annotated methods on the source object. 43 | * 44 | * @param graphQLObjectMapper the current IGraphQLObjectMapper 45 | * @param targetObject the target object the DataFetcher will invoke the method on. 46 | * @return 47 | */ 48 | @Override 49 | public List newMethodQueriesForObject(IGraphQLObjectMapper graphQLObjectMapper, Object targetObject) { 50 | List results = Lists.newLinkedList(); 51 | 52 | results.addAll(graphQLObjectMapper.getGraphQLFieldDefinitions( 53 | Optional.of(targetObject), 54 | targetObject.getClass(), 55 | targetObject.getClass(), 56 | Optional.of(AnnotationUtils.getFieldsWithAnnotation(targetObject.getClass(), GraphQLQuery.class)), 57 | Optional.of(AnnotationUtils.getMethodsWithAnnotation(targetObject.getClass(), GraphQLQuery.class)))); 58 | 59 | return results; 60 | } 61 | 62 | protected GraphQLOutputType getReturnType(IGraphQLObjectMapper graphQLObjectMapper, Method method) { 63 | return graphQLObjectMapper.getOutputType(method.getGenericReturnType()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/impl/FullTypeNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | /** 8 | * Generates type names using the full package name with . replaced with _ 9 | */ 10 | public class FullTypeNamingStrategy extends SimpleTypeNamingStrategy { 11 | 12 | @Override 13 | public String getTypeName(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 14 | Class theClass = graphQLObjectMapper.getClassFromType(type); 15 | String typeName = super.getTypeName(graphQLObjectMapper, type); 16 | 17 | if (theClass.getPackage() != null) { 18 | return String.format("%s%s%s", 19 | theClass.getPackage().getName().replace(".", this.getDelimiter()), 20 | this.getDelimiter(), 21 | typeName); 22 | } else { 23 | return typeName; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/impl/RelayTypeNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLName; 5 | 6 | import java.lang.reflect.ParameterizedType; 7 | import java.lang.reflect.Type; 8 | 9 | /** 10 | * Created by bpatterson on 2/10/16. 11 | */ 12 | public class RelayTypeNamingStrategy extends SimpleTypeNamingStrategy { 13 | 14 | public RelayTypeNamingStrategy(String delimiter, String inputTypePostfix) { 15 | super(delimiter, inputTypePostfix); 16 | } 17 | 18 | public RelayTypeNamingStrategy() { 19 | } 20 | 21 | @Override 22 | public String getTypeName(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 23 | String typeString; 24 | Class theClass = graphQLObjectMapper.getClassFromType(type); 25 | 26 | GraphQLName typeName = theClass.getAnnotation(GraphQLName.class); 27 | if (typeName != null) { 28 | return typeName.name(); 29 | } 30 | 31 | // start with the class name 32 | typeString = theClass.getSimpleName(); 33 | // for parameterized types append the parameter types to build unique type 34 | if (type instanceof ParameterizedType) { 35 | ParameterizedType pType = (ParameterizedType) type; 36 | 37 | // relay has special handling for types that end with Connection so we need to 38 | // preserve the types trailing connection in this scenario 39 | if (typeString.toLowerCase().endsWith("connection")) { 40 | // we only want the part of the typeString without the trailing connection 41 | String prefix = typeString.substring(0, typeString.toLowerCase().lastIndexOf("connection")); 42 | String suffix = typeString.substring(typeString.toLowerCase().lastIndexOf("connection"), typeString.length()); 43 | // RelayConnection --> Relay_String_Connection 44 | if (prefix.length() > 0) { 45 | typeString = String.format("%s%s%s%s%s", 46 | prefix, 47 | this.getDelimiter(), 48 | getParametersTypeString(graphQLObjectMapper, pType), 49 | this.getDelimiter(), 50 | suffix); 51 | } else { 52 | typeString = String.format("%s%s%s", 53 | getParametersTypeString(graphQLObjectMapper, pType), 54 | this.getDelimiter(), 55 | suffix); 56 | 57 | } 58 | } else { 59 | typeString = String.format("%s%s%s", 60 | typeString, 61 | this.getDelimiter(), 62 | getParametersTypeString(graphQLObjectMapper, pType)); 63 | } 64 | } 65 | 66 | return typeString; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/impl/SimpleTypeFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.ITypeFactory; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | /** 8 | * Created by bpatterson on 3/1/16. 9 | */ 10 | public class SimpleTypeFactory implements ITypeFactory { 11 | 12 | @Override 13 | public Object convertToType(final Type type, final Object arg) { 14 | return arg; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/impl/SimpleTypeNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.ITypeNamingStrategy; 5 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLName; 6 | import com.google.common.base.Preconditions; 7 | 8 | import java.lang.reflect.ParameterizedType; 9 | import java.lang.reflect.Type; 10 | 11 | /** 12 | * Type naming strategy that uses the simple class name as the GraphQL type. 13 | */ 14 | public class SimpleTypeNamingStrategy implements ITypeNamingStrategy { 15 | 16 | private final String delimiter; 17 | private final String inputTypePostfix; 18 | 19 | public SimpleTypeNamingStrategy(String delimiter, String inputTypePostfix) { 20 | Preconditions.checkNotNull(delimiter, "Delimiter cannot be null."); 21 | Preconditions.checkNotNull(inputTypePostfix, "InputType Postfix cannot be null."); 22 | this.delimiter = delimiter; 23 | this.inputTypePostfix = inputTypePostfix; 24 | } 25 | 26 | public SimpleTypeNamingStrategy() { 27 | this("_", "Input"); 28 | } 29 | 30 | @Override 31 | public String getTypeName(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 32 | String typeString; 33 | Class theClass = graphQLObjectMapper.getClassFromType(type); 34 | 35 | GraphQLName typeName = theClass.getAnnotation(GraphQLName.class); 36 | if (typeName != null) { 37 | return typeName.name(); 38 | } 39 | 40 | // start with the class name 41 | typeString = theClass.getSimpleName(); 42 | 43 | // for parameterized types append the parameter types to build unique type 44 | if (type instanceof ParameterizedType) { 45 | ParameterizedType pType = (ParameterizedType) type; 46 | 47 | typeString += this.getDelimiter() + 48 | getParametersTypeString(graphQLObjectMapper, pType); 49 | } 50 | 51 | return typeString; 52 | } 53 | 54 | String getParametersTypeString(IGraphQLObjectMapper graphQLObjectMapper, ParameterizedType type) { 55 | String parametersTypeString = ""; 56 | Type[] subTypes = type.getActualTypeArguments(); 57 | for (Type subType : subTypes) { 58 | if (parametersTypeString.length() != 0) { 59 | parametersTypeString += this.getDelimiter(); 60 | } 61 | parametersTypeString += graphQLObjectMapper.getClassFromType(subType).getSimpleName(); 62 | if (subType instanceof ParameterizedType) { 63 | parametersTypeString += this.getDelimiter() + 64 | getParametersTypeString(graphQLObjectMapper, (ParameterizedType) subType); 65 | } 66 | } 67 | 68 | return parametersTypeString; 69 | } 70 | 71 | @Override 72 | public String getInputTypePostfix() { 73 | return inputTypePostfix; 74 | } 75 | 76 | @Override 77 | public String getDelimiter() { 78 | return delimiter; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/ConnectionCursor.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | 4 | public class ConnectionCursor { 5 | 6 | private String value; 7 | 8 | public ConnectionCursor() { 9 | setValue(null); 10 | } 11 | 12 | public ConnectionCursor(String value) { 13 | this.setValue(value); 14 | } 15 | 16 | public String getValue() { 17 | return value; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | 23 | 24 | if (this == o) return true; 25 | if (o == null || getClass() != o.getClass()) return false; 26 | 27 | ConnectionCursor that = (ConnectionCursor) o; 28 | 29 | if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) return false; 30 | 31 | return true; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return getValue() != null ? getValue().hashCode() : 0; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return getValue(); 42 | } 43 | 44 | public void setValue(String value) { 45 | this.value = value; 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/Edge.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | 4 | import java.util.Objects; 5 | 6 | /** 7 | * Generic type for representing a Relay Edge connection. 8 | */ 9 | public class Edge { 10 | 11 | T node; 12 | ConnectionCursor cursor; 13 | 14 | public Edge() { 15 | 16 | } 17 | 18 | public Edge(T node, ConnectionCursor cursor) { 19 | this.node = node; 20 | this.cursor = cursor; 21 | } 22 | 23 | public T getNode() { 24 | return node; 25 | } 26 | 27 | public void setNode(T node) { 28 | this.node = node; 29 | } 30 | 31 | public ConnectionCursor getCursor() { 32 | return cursor; 33 | } 34 | 35 | public void setCursor(ConnectionCursor cursor) { 36 | this.cursor = cursor; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | 42 | if (this == o) 43 | return true; 44 | if (o == null || getClass() != o.getClass()) 45 | return false; 46 | 47 | Edge that = (Edge) o; 48 | 49 | return Objects.equals(node, that.node) && Objects.equals(cursor, that.cursor); 50 | 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(node, cursor); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/INode.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLName; 4 | 5 | /** 6 | * All GraphQL types used in relay must implement this interface 7 | * in order to get support for Relay's node call to retrieve objects. 8 | */ 9 | @GraphQLName(name="Node") 10 | public interface INode { 11 | 12 | String getId(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/IRelayNodeFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | /** 4 | * Interface for node factories that know how to construct objects from the appropriate node id. 5 | */ 6 | public interface IRelayNodeFactory { 7 | 8 | /** 9 | * This method should return true only if the specified object id should be handled by this factory 10 | */ 11 | boolean handlesNodeId(String id); 12 | 13 | /** 14 | * Accepts a Relay ID and constructs an object based on the ID. 15 | * @param objectId the relay object ID. All object ID's must be prefixed with the factories name 16 | * @return 17 | */ 18 | INode newObjectFromID(String objectId); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/IRelayNodeHandler.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | /** 4 | * A GrapphQLController annotated object that handles Relay node(id: string) requests 5 | * to the server. 6 | */ 7 | public interface IRelayNodeHandler { 8 | 9 | /** 10 | * This method finds an existing object by it's node id. The should delegate to the registered 11 | * IRelayNodeFactory appropriate for this node type. 12 | * @param id 13 | * @return 14 | */ 15 | INode findNodeById(String id); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/PageInfo.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | public class PageInfo { 4 | private ConnectionCursor startCursor; 5 | private ConnectionCursor endCursor; 6 | private boolean hasPreviousPage; 7 | private boolean hasNextPage; 8 | 9 | public ConnectionCursor getStartCursor() { 10 | return startCursor; 11 | } 12 | 13 | public void setStartCursor(ConnectionCursor startCursor) { 14 | this.startCursor = startCursor; 15 | } 16 | 17 | public ConnectionCursor getEndCursor() { 18 | return endCursor; 19 | } 20 | 21 | public void setEndCursor(ConnectionCursor endCursor) { 22 | this.endCursor = endCursor; 23 | } 24 | 25 | public boolean isHasPreviousPage() { 26 | return hasPreviousPage; 27 | } 28 | 29 | public void setHasPreviousPage(boolean hasPreviousPage) { 30 | this.hasPreviousPage = hasPreviousPage; 31 | } 32 | 33 | public boolean isHasNextPage() { 34 | return hasNextPage; 35 | } 36 | 37 | public void setHasNextPage(boolean hasNextPage) { 38 | this.hasNextPage = hasNextPage; 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/RelayConnection.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Generic class used for representing Relay Connection Objects 7 | */ 8 | public class RelayConnection { 9 | private List> edges; 10 | private PageInfo pageInfo; 11 | 12 | public List> getEdges() { 13 | return edges; 14 | } 15 | 16 | public RelayConnection setEdges(List> edges) { 17 | this.edges = edges; 18 | 19 | return this; 20 | } 21 | 22 | public PageInfo getPageInfo() { 23 | return pageInfo; 24 | } 25 | 26 | public RelayConnection setPageInfo(PageInfo pageInfo) { 27 | this.pageInfo = pageInfo; 28 | 29 | return this; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/annotations/RelayNodeFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This annotation should be used for Relay Node Factory methods that know 10 | * how to process node requests. 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.TYPE}) 14 | public @interface RelayNodeFactory { 15 | 16 | /** 17 | * Types of objects this node factory implements. 18 | * @return 19 | */ 20 | Class[] types(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/exceptions/UnknownObjectType.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.exceptions; 2 | 3 | /** 4 | * When constructing Node objects this is thrown when we are unable to construct the object from the specified ID. 5 | */ 6 | public class UnknownObjectType extends RuntimeException{ 7 | private static final long serialVersionUID = -5858938084329702803L; 8 | 9 | public UnknownObjectType(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/relay/impl/RelayDefaultNodeHandler.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLController; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLName; 5 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLParam; 6 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLQuery; 7 | import com.bretpatterson.schemagen.graphql.relay.INode; 8 | import com.google.common.collect.ImmutableList; 9 | import com.bretpatterson.schemagen.graphql.relay.IRelayNodeFactory; 10 | import com.bretpatterson.schemagen.graphql.relay.IRelayNodeHandler; 11 | import com.bretpatterson.schemagen.graphql.relay.exceptions.UnknownObjectType; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * This is the default handler for all node query requests for relay. 17 | */ 18 | @GraphQLController 19 | public class RelayDefaultNodeHandler implements IRelayNodeHandler { 20 | private 21 | List nodeFactories; 22 | 23 | RelayDefaultNodeHandler(List< IRelayNodeFactory> nodeFactories) { 24 | this.nodeFactories = nodeFactories; 25 | } 26 | 27 | @Override 28 | @GraphQLQuery 29 | @GraphQLName(name="node") 30 | public INode findNodeById(@GraphQLParam(name="id") String nodeId) { 31 | 32 | for (IRelayNodeFactory factory : nodeFactories) { 33 | if (factory.handlesNodeId(nodeId)) { 34 | return factory.newObjectFromID(nodeId); 35 | } 36 | } 37 | throw new UnknownObjectType(String.format("Unable to map id %s to a valid data type", nodeId)); 38 | } 39 | 40 | public static Builder builder() { 41 | return new Builder(); 42 | } 43 | 44 | public static class Builder { 45 | ImmutableList.Builder nodeFactoriesBuilder = ImmutableList.builder(); 46 | 47 | public Builder registerFactory(IRelayNodeFactory factory) { 48 | nodeFactoriesBuilder.add(factory); 49 | 50 | return this; 51 | } 52 | 53 | public RelayDefaultNodeHandler build() { 54 | return new RelayDefaultNodeHandler(nodeFactoriesBuilder.build()); 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/IGraphQLTypeMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import graphql.schema.GraphQLInputType; 5 | import graphql.schema.GraphQLOutputType; 6 | 7 | import java.lang.reflect.Type; 8 | 9 | /** 10 | * Used to implement your own customer Object {@literal --->} GraphQLType. 11 | */ 12 | public interface IGraphQLTypeMapper { 13 | 14 | /** 15 | * Only used when you are seeking to handle all types that implement a specific interface. 16 | * For example if you want to write a CollectionTypeMapper you would implement this method 17 | * and then register your implementation as an interface based type mapper. These are more expensive 18 | * to process because we don't map from type {@literal -->} mapper but instead search for the first registered 19 | * interface mapper that handles the specified type. 20 | * 21 | * @param graphQLObjectMapper 22 | * @param type 23 | * @return 24 | */ 25 | boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type); 26 | 27 | /** 28 | * Convert the specified Type to to a GraphQLOutputType. 29 | * @param graphQLObjectMapper 30 | * @param type 31 | * @return 32 | */ 33 | GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type); 34 | 35 | GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/lang/CharSequenceMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.lang; 2 | 3 | import java.lang.reflect.Type; 4 | 5 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 6 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 7 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 8 | 9 | import graphql.Scalars; 10 | import graphql.schema.GraphQLInputType; 11 | import graphql.schema.GraphQLOutputType; 12 | 13 | /** 14 | * Typemapper for Enum. Maps it to a generic string type. 15 | */ 16 | @GraphQLTypeMapper(type = CharSequence.class) 17 | public class CharSequenceMapper implements IGraphQLTypeMapper { 18 | 19 | @Override 20 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 21 | return CharSequence.class.isAssignableFrom(graphQLObjectMapper.getClassFromType(type)); 22 | } 23 | 24 | @Override 25 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper objectMapper, Type type) { 26 | return Scalars.GraphQLString; 27 | } 28 | 29 | @Override 30 | public GraphQLInputType getInputType(IGraphQLObjectMapper objectMapper, Type type) { 31 | return Scalars.GraphQLString; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/lang/EnumMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.lang; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.Scalars; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | /** 13 | * Typemapper for Enum. Maps it to a generic string type. 14 | */ 15 | @GraphQLTypeMapper(type = Enum.class) 16 | public class EnumMapper implements IGraphQLTypeMapper { 17 | 18 | @Override 19 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 20 | return Enum.class.isAssignableFrom(graphQLObjectMapper.getClassFromType(type)); 21 | } 22 | 23 | @Override 24 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper objectMapper, Type type) { 25 | return Scalars.GraphQLString; 26 | } 27 | 28 | @Override 29 | public GraphQLInputType getInputType(IGraphQLObjectMapper objectMapper, Type type) { 30 | return Scalars.GraphQLString; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/math/BigDecimalMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.math; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.Scalars; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | 10 | import java.lang.reflect.Type; 11 | import java.math.BigDecimal; 12 | 13 | /** 14 | * Type Mapper that converts a BigDecimal.class type to a GraphQLFloat data type 15 | */ 16 | @GraphQLTypeMapper(type= BigDecimal.class) 17 | public class BigDecimalMapper implements IGraphQLTypeMapper{ 18 | @Override 19 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 20 | return type == BigDecimal.class; 21 | } 22 | 23 | @Override 24 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 25 | return Scalars.GraphQLFloat; 26 | } 27 | 28 | @Override 29 | public GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 30 | return Scalars.GraphQLFloat; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/math/BigIntegerMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.math; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.Scalars; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | 10 | import java.lang.reflect.Type; 11 | import java.math.BigInteger; 12 | 13 | /** 14 | * DefaultType Mapper to convert a BigInteger to a GraphQLLong data type. 15 | */ 16 | @GraphQLTypeMapper(type= BigInteger.class) 17 | public class BigIntegerMapper implements IGraphQLTypeMapper{ 18 | @Override 19 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 20 | return type == BigInteger.class; 21 | } 22 | 23 | @Override 24 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 25 | return Scalars.GraphQLLong; 26 | } 27 | 28 | @Override 29 | public GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 30 | return Scalars.GraphQLLong; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/net/URIMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.net; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.Scalars; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | 10 | import java.lang.reflect.Type; 11 | import java.net.URI; 12 | 13 | 14 | /** 15 | * Default URI mapper that converts a URI to a GraphQLString 16 | */ 17 | @GraphQLTypeMapper(type=URI.class) 18 | public class URIMapper implements IGraphQLTypeMapper { 19 | 20 | @Override 21 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 22 | return type == URI.class; 23 | } 24 | @Override 25 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper objectMapper, Type type) { 26 | return Scalars.GraphQLString; 27 | } 28 | 29 | @Override 30 | public GraphQLInputType getInputType(IGraphQLObjectMapper objectMapper, Type type) { 31 | return Scalars.GraphQLString; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/util/ArrayMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.util; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.schema.GraphQLInputType; 7 | import graphql.schema.GraphQLList; 8 | import graphql.schema.GraphQLOutputType; 9 | 10 | import java.lang.reflect.Array; 11 | import java.lang.reflect.Type; 12 | 13 | /** 14 | * Default Interface type mapper that convers all types of Array to 15 | * a GraphQLList of the array type. 16 | */ 17 | @GraphQLTypeMapper(type = Array.class) 18 | public class ArrayMapper implements IGraphQLTypeMapper { 19 | @Override 20 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 21 | Class typeClass = graphQLObjectMapper.getClassFromType(type); 22 | return Array.class.isAssignableFrom(typeClass); 23 | } 24 | 25 | @Override 26 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 27 | Class classType = (Class) type; 28 | return new GraphQLList(graphQLObjectMapper.getOutputType(classType.getComponentType())); 29 | } 30 | 31 | @Override 32 | public GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 33 | Class classType = (Class) type; 34 | return new GraphQLList(graphQLObjectMapper.getInputType(classType.getComponentType())); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/util/CollectionMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.util; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.schema.GraphQLInputType; 7 | import graphql.schema.GraphQLList; 8 | import graphql.schema.GraphQLOutputType; 9 | 10 | import java.lang.reflect.ParameterizedType; 11 | import java.lang.reflect.Type; 12 | import java.util.Collection; 13 | 14 | /** 15 | * Default interface mapper for all Collections. Converts all collections 16 | * into a GraphQLList type containing the type of object the collection contains. 17 | */ 18 | @GraphQLTypeMapper(type = Collection.class) 19 | public class CollectionMapper implements IGraphQLTypeMapper { 20 | @Override 21 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 22 | Class typeClass = graphQLObjectMapper.getClassFromType(type); 23 | return Collection.class.isAssignableFrom(typeClass); 24 | } 25 | 26 | @Override 27 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 28 | if (type instanceof ParameterizedType) { 29 | ParameterizedType parameterizedType = (ParameterizedType) type; 30 | return new GraphQLList(graphQLObjectMapper.getOutputType(parameterizedType.getActualTypeArguments()[0])); 31 | } else { 32 | return new GraphQLList(graphQLObjectMapper.getOutputType(Object.class)); 33 | } 34 | } 35 | 36 | @Override 37 | public GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 38 | if (type instanceof ParameterizedType) { 39 | ParameterizedType parameterizedType = (ParameterizedType) type; 40 | return new GraphQLList(graphQLObjectMapper.getInputType(parameterizedType.getActualTypeArguments()[0])); 41 | } else { 42 | return new GraphQLList(graphQLObjectMapper.getInputType(Object.class)); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/util/DateMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.util; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.Scalars; 7 | import graphql.schema.GraphQLFieldDefinition; 8 | import graphql.schema.GraphQLInputObjectField; 9 | import graphql.schema.GraphQLInputObjectType; 10 | import graphql.schema.GraphQLInputType; 11 | import graphql.schema.GraphQLObjectType; 12 | import graphql.schema.GraphQLOutputType; 13 | 14 | import java.lang.reflect.Type; 15 | import java.util.Date; 16 | 17 | /** 18 | * Default Date Mapper that converts a Date to a GraphQL Long 19 | */ 20 | @GraphQLTypeMapper(type= Date.class) 21 | public class DateMapper implements IGraphQLTypeMapper { 22 | @Override 23 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 24 | return type == Date.class; 25 | } 26 | 27 | @Override 28 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper objectMapper, Type type) { 29 | return GraphQLObjectType.newObject().name(objectMapper.getTypeNamingStrategy().getTypeName(objectMapper, type)) 30 | .field(GraphQLFieldDefinition.newFieldDefinition().name("time").type(Scalars.GraphQLString).build()).build(); 31 | 32 | } 33 | 34 | @Override 35 | public GraphQLInputType getInputType(IGraphQLObjectMapper objectMapper, Type type) { 36 | return GraphQLInputObjectType.newInputObject().name(objectMapper.getTypeNamingStrategy().getTypeName(objectMapper, type)) 37 | .field(GraphQLInputObjectField.newInputObjectField().name("time").type(Scalars.GraphQLString).build()).build(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/util/EnumSetMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.util; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.schema.GraphQLEnumType; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLList; 9 | import graphql.schema.GraphQLOutputType; 10 | 11 | import java.lang.reflect.Type; 12 | import java.util.EnumSet; 13 | 14 | /** 15 | * Default interface type mapper that handles all types of EnumSet. 16 | */ 17 | @GraphQLTypeMapper(type = EnumSet.class) 18 | public class EnumSetMapper implements IGraphQLTypeMapper { 19 | @Override 20 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 21 | Class typeClass = graphQLObjectMapper.getClassFromType(type); 22 | return EnumSet.class.isAssignableFrom(typeClass); 23 | } 24 | 25 | @Override 26 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 27 | Class classType = (Class) type; 28 | Class enumClassType = classType.getComponentType(); 29 | GraphQLEnumType.Builder enumType = GraphQLEnumType.newEnum() 30 | .name(graphQLObjectMapper.getTypeNamingStrategy().getTypeName(graphQLObjectMapper, enumClassType)); 31 | 32 | for (Object value : enumClassType.getEnumConstants()) { 33 | enumType.value(value.toString(), value); 34 | } 35 | return new GraphQLList(enumType.build()); 36 | 37 | } 38 | 39 | @Override 40 | public GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 41 | Class classType = (Class) type; 42 | Class enumClassType = classType.getComponentType(); 43 | GraphQLEnumType.Builder enumType = GraphQLEnumType.newEnum() 44 | .name(graphQLObjectMapper.getTypeNamingStrategy().getTypeName(graphQLObjectMapper, enumClassType)); 45 | 46 | for (Object value : enumClassType.getEnumConstants()) { 47 | enumType.value(value.toString(), value); 48 | } 49 | return new GraphQLList(enumType.build()); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/util/MapMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.util; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.exceptions.NotMappableException; 6 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 7 | import graphql.schema.GraphQLFieldDefinition; 8 | import graphql.schema.GraphQLInputObjectField; 9 | import graphql.schema.GraphQLInputObjectType; 10 | import graphql.schema.GraphQLInputType; 11 | import graphql.schema.GraphQLList; 12 | import graphql.schema.GraphQLObjectType; 13 | import graphql.schema.GraphQLOutputType; 14 | 15 | import java.lang.reflect.ParameterizedType; 16 | import java.lang.reflect.Type; 17 | import java.util.Map; 18 | 19 | /** 20 | * GraphQL doesn't support generic maps fully. However this implementation attempts to support them as best it can. It currently supports 21 | * {@code Map } since the set of keys is well defined. In this case it maps this datatype to an Object of Enum {@literal -->} GraphQLType 22 | * where the keys of Enum are fields and values are the field values. 23 | */ 24 | @GraphQLTypeMapper(type = Map.class) 25 | public class MapMapper implements IGraphQLTypeMapper { 26 | 27 | public static final String KEY_NAME = "key"; 28 | public static final String VALUE_NAME = "value"; 29 | 30 | @Override 31 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 32 | Class typeClass = graphQLObjectMapper.getClassFromType(type); 33 | return Map.class.isAssignableFrom(typeClass); 34 | } 35 | 36 | @Override 37 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 38 | if (type instanceof ParameterizedType) { 39 | return getListOutputMapping(graphQLObjectMapper, type); 40 | } else { 41 | throw new NotMappableException(String.format("%s is not mappable to GraphQL", graphQLObjectMapper.getTypeNamingStrategy().getTypeName(graphQLObjectMapper, type))); 42 | } 43 | } 44 | 45 | private GraphQLOutputType getListOutputMapping(final IGraphQLObjectMapper graphQLObjectMapper, final Type type) { 46 | ParameterizedType pType = (ParameterizedType) type; 47 | GraphQLObjectType objectType = GraphQLObjectType.newObject() 48 | .name(graphQLObjectMapper.getTypeNamingStrategy().getTypeName(graphQLObjectMapper, type)) 49 | .field(GraphQLFieldDefinition.newFieldDefinition() 50 | .name(KEY_NAME) 51 | .type(graphQLObjectMapper.getOutputType(pType.getActualTypeArguments()[0])) 52 | .build()) 53 | .field(GraphQLFieldDefinition.newFieldDefinition() 54 | .name(VALUE_NAME) 55 | .type(graphQLObjectMapper.getOutputType(pType.getActualTypeArguments()[1])) 56 | .build()) 57 | .build(); 58 | 59 | return new GraphQLList(objectType); 60 | } 61 | 62 | private GraphQLInputType getListInputMapping(final IGraphQLObjectMapper graphQLObjectMapper, final Type type) { 63 | ParameterizedType pType = (ParameterizedType) type; 64 | GraphQLInputObjectType objectType = GraphQLInputObjectType.newInputObject() 65 | .name(graphQLObjectMapper.getTypeNamingStrategy().getTypeName(graphQLObjectMapper, type)) 66 | .field(GraphQLInputObjectField.newInputObjectField() 67 | .name(KEY_NAME) 68 | .type(graphQLObjectMapper.getInputType(pType.getActualTypeArguments()[0])) 69 | .build()) 70 | .field(GraphQLInputObjectField.newInputObjectField() 71 | .name(VALUE_NAME) 72 | .type(graphQLObjectMapper.getInputType(pType.getActualTypeArguments()[1])) 73 | .build()) 74 | .build(); 75 | 76 | return new GraphQLList(objectType); 77 | } 78 | 79 | @Override 80 | public GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 81 | if (type instanceof ParameterizedType) { 82 | return getListInputMapping(graphQLObjectMapper, type); 83 | } else { 84 | throw new NotMappableException(String.format("%s is not mappable to GraphQL", graphQLObjectMapper.getTypeNamingStrategy().getTypeName(graphQLObjectMapper, type))); 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/java/util/TimeZoneMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.java.util; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 6 | import graphql.Scalars; 7 | import graphql.schema.GraphQLInputType; 8 | import graphql.schema.GraphQLOutputType; 9 | 10 | import java.lang.reflect.Type; 11 | import java.util.TimeZone; 12 | 13 | /** 14 | * Maps a TimeZone object into a GraphQLString 15 | */ 16 | @GraphQLTypeMapper(type=TimeZone.class) 17 | public class TimeZoneMapper implements IGraphQLTypeMapper { 18 | 19 | @Override 20 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 21 | return type == TimeZone.class; 22 | } 23 | 24 | @Override 25 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper objectMapper, Type type) { 26 | return Scalars.GraphQLString; 27 | } 28 | 29 | @Override 30 | public GraphQLInputType getInputType(IGraphQLObjectMapper objectMapper, Type type) { 31 | return Scalars.GraphQLString; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/typemappers/relay/ConnectionCursorMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.relay; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 5 | import com.bretpatterson.schemagen.graphql.relay.ConnectionCursor; 6 | import com.bretpatterson.schemagen.graphql.typemappers.IGraphQLTypeMapper; 7 | import graphql.Scalars; 8 | import graphql.schema.GraphQLInputType; 9 | import graphql.schema.GraphQLOutputType; 10 | 11 | import java.lang.reflect.Type; 12 | 13 | /** 14 | * Specification Compliant ConnectionCursor mapper 15 | * 16 | * @see Connection Specification 17 | */ 18 | @GraphQLTypeMapper(type = ConnectionCursor.class) 19 | public class ConnectionCursorMapper implements IGraphQLTypeMapper { 20 | 21 | @Override 22 | public boolean handlesType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 23 | return ConnectionCursor.class.isAssignableFrom(graphQLObjectMapper.getClassFromType(type)); 24 | } 25 | 26 | @Override 27 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 28 | return Scalars.GraphQLString; 29 | } 30 | 31 | @Override 32 | public GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 33 | return Scalars.GraphQLString; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/bretpatterson/schemagen/graphql/utils/AnnotationUtils.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.utils; 2 | 3 | import com.bretpatterson.schemagen.graphql.datafetchers.DefaultMethodDataFetcher; 4 | import com.google.common.base.Throwables; 5 | import com.google.common.collect.ImmutableList; 6 | import com.google.common.collect.ImmutableMap; 7 | import com.google.common.collect.ImmutableSet; 8 | import com.google.common.reflect.ClassPath; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | import java.lang.annotation.Annotation; 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.Method; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * Common Annotation related utility methods. 21 | */ 22 | public class AnnotationUtils { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(AnnotationUtils.class); 25 | public static final String DEFAULT_NULL = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"; 26 | private static ClassLoader classLoader; 27 | private static ClassPath classPath; 28 | 29 | public final static class DEFAULT_NULL_CLASS extends DefaultMethodDataFetcher {} 30 | static { 31 | try { 32 | // Change classloader to work with spring boot executable jars 33 | // http://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html#executable-jar-system-classloader 34 | classLoader = Thread.currentThread().getContextClassLoader(); 35 | classPath = ClassPath.from(classLoader); 36 | } catch (IOException ex) { 37 | Throwables.propagate(ex); 38 | } 39 | } 40 | @SuppressWarnings("unchecked") 41 | public static T findAnnotation(Annotation[] annotations, Class type) { 42 | for (Annotation annotation : annotations) { 43 | if (annotation.annotationType() == type) { 44 | return (T) annotation; 45 | } 46 | } 47 | return null; 48 | } 49 | 50 | public static Map, T> getClassesWithAnnotation(Class annotation, String packageName) { 51 | ImmutableMap.Builder, T> results = ImmutableMap.builder(); 52 | try { 53 | ImmutableSet classes = classPath.getTopLevelClassesRecursive(packageName); 54 | for (ClassPath.ClassInfo info : classes) { 55 | try { 56 | Class type = info.load(); 57 | T classAnnotation = type.getAnnotation(annotation); 58 | if (classAnnotation != null) { 59 | LOGGER.info("Found {} with annotation {}.", type.getCanonicalName(), annotation.getClass()); 60 | results.put(type, classAnnotation); 61 | } 62 | } catch (NoClassDefFoundError ex) { 63 | LOGGER.warn("Failed to load {}. This is probably because of an unsatisfied runtime dependency.", ex); 64 | } 65 | } 66 | } catch (Exception ex) { 67 | Throwables.propagate(ex); 68 | } 69 | 70 | return results.build(); 71 | } 72 | 73 | public static List getMethodsWithAnnotation(Class targetClass, Class annotationClass) { 74 | ImmutableList.Builder methods = ImmutableList.builder(); 75 | 76 | for (Method method : targetClass.getDeclaredMethods()) { 77 | T annotation = method.getAnnotation(annotationClass); 78 | if (annotation != null) { 79 | methods.add(method); 80 | } 81 | } 82 | 83 | return methods.build(); 84 | } 85 | 86 | public static List getFieldsWithAnnotation(Class targetClass, Class annotationClass) { 87 | ImmutableList.Builder fields = ImmutableList.builder(); 88 | 89 | for (Field field : targetClass.getDeclaredFields()) { 90 | T annotation = field.getAnnotation(annotationClass); 91 | if (annotation != null) { 92 | fields.add(field); 93 | } 94 | } 95 | 96 | return fields.build(); 97 | } 98 | 99 | public static boolean isNullValue(String value) { 100 | return DEFAULT_NULL.equals(value); 101 | } 102 | 103 | public static boolean isNullValue(Class value) { 104 | return DEFAULT_NULL_CLASS.class.equals(value); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/GraphQLSchemaBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLController; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLDescription; 5 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLMutation; 6 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLParam; 7 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLQuery; 8 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeConverter; 9 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLName; 10 | import com.bretpatterson.schemagen.graphql.datafetchers.DefaultTypeConverter; 11 | import com.bretpatterson.schemagen.graphql.impl.common.JacksonTypeFactory; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.google.common.collect.ImmutableList; 14 | import graphql.ExecutionResult; 15 | import graphql.GraphQL; 16 | import graphql.Scalars; 17 | import graphql.schema.DataFetcher; 18 | import graphql.schema.GraphQLFieldDefinition; 19 | import graphql.schema.GraphQLInputObjectType; 20 | import graphql.schema.GraphQLObjectType; 21 | import graphql.schema.GraphQLSchema; 22 | import org.junit.Test; 23 | 24 | import java.util.Map; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertNotNull; 28 | 29 | /** 30 | * Created by bpatterson on 1/23/16. 31 | */ 32 | public class GraphQLSchemaBuilderTest { 33 | 34 | @GraphQLName(name = "RenamedTestInputType") 35 | private class RenameMe { 36 | 37 | } 38 | 39 | private class TestType { 40 | 41 | String myfield; 42 | } 43 | 44 | @GraphQLController 45 | public class TestController { 46 | 47 | String name = ""; 48 | 49 | @GraphQLQuery(name = "testType") 50 | public TestType getTestType() { 51 | return new TestType(); 52 | } 53 | 54 | @GraphQLQuery(name = "testQueryArguments") 55 | public String testQueryArguments(@GraphQLParam(name = "string") String stringType, 56 | @GraphQLParam(name = "int") Integer intType, 57 | @GraphQLParam(name = "test") RenameMe testType) { 58 | return ""; 59 | } 60 | 61 | @GraphQLMutation 62 | public String setName(@GraphQLParam(name = "name") String name) { 63 | this.name = name; 64 | 65 | return this.name; 66 | } 67 | 68 | @GraphQLQuery 69 | public String getName() { 70 | return name; 71 | } 72 | } 73 | 74 | @Test 75 | public void testController() { 76 | GraphQLSchema schema = GraphQLSchemaBuilder.newBuilder().registerGraphQLControllerObjects(ImmutableList. of(new TestController())).build(); 77 | 78 | GraphQLFieldDefinition queryType = schema.getQueryType().getFieldDefinition("testType"); 79 | assertEquals("testType", queryType.getName()); 80 | assertEquals("TestType", queryType.getType().getName()); 81 | } 82 | 83 | @Test 84 | public void testQueryArguments() { 85 | GraphQLSchema schema = GraphQLSchemaBuilder.newBuilder().registerGraphQLControllerObjects(ImmutableList. of(new TestController())).build(); 86 | 87 | GraphQLFieldDefinition queryType = schema.getQueryType().getFieldDefinition("testQueryArguments"); 88 | assertEquals("testQueryArguments", queryType.getName()); 89 | assertEquals(Scalars.GraphQLString, queryType.getArgument("string").getType()); 90 | assertEquals(Scalars.GraphQLInt, queryType.getArgument("int").getType()); 91 | assertEquals(GraphQLInputObjectType.class, queryType.getArgument("test").getType().getClass()); 92 | assertEquals("RenamedTestInputType_Input", queryType.getArgument("test").getType().getName()); 93 | } 94 | 95 | @SuppressWarnings("unchecked") 96 | @Test 97 | public void testMutation() { 98 | GraphQLSchema schema = GraphQLSchemaBuilder.newBuilder() 99 | .registerTypeFactory(new JacksonTypeFactory(new ObjectMapper())) 100 | .registerGraphQLControllerObjects(ImmutableList. of(new TestController())) 101 | .build(); 102 | 103 | GraphQLFieldDefinition mutationType = schema.getMutationType().getFieldDefinition("setName"); 104 | assertEquals("setName", mutationType.getName()); 105 | assertEquals(Scalars.GraphQLString, mutationType.getArgument("name").getType()); 106 | 107 | ExecutionResult result = new GraphQL(schema).execute("mutation M { setName(name: \"The new name\") }"); 108 | 109 | String newName = ((Map) result.getData()).get("setName"); 110 | assertEquals(0, result.getErrors().size()); 111 | assertEquals("The new name", newName); 112 | 113 | result = new GraphQL(schema).execute("query Q { getName }"); 114 | 115 | newName = ((Map) result.getData()).get("getName"); 116 | assertEquals(0, result.getErrors().size()); 117 | assertEquals("The new name", newName); 118 | } 119 | 120 | @GraphQLController(rootQueriesObjectName = "QueriesScope", rootMutationsObjectName = "MutationsScope") 121 | private class ControllerScoping { 122 | 123 | @GraphQLQuery(name = "query1") 124 | public String query1(@GraphQLParam(name = "param1") String param1) { 125 | return param1; 126 | } 127 | 128 | @GraphQLMutation(name = "mutation1") 129 | public String mutation1(@GraphQLParam(name = "param1") String param1) { 130 | return param1; 131 | } 132 | 133 | } 134 | 135 | @Test 136 | public void testControllerObjectScoping() { 137 | GraphQLSchema schema = GraphQLSchemaBuilder.newBuilder() 138 | .registerTypeFactory(new JacksonTypeFactory(new ObjectMapper())) 139 | .registerGraphQLControllerObjects(ImmutableList. of(new ControllerScoping())) 140 | .build(); 141 | 142 | // validate query scope 143 | GraphQLFieldDefinition queryScope = schema.getQueryType().getFieldDefinition("QueriesScope"); 144 | assertNotNull(queryScope); 145 | assertEquals(GraphQLObjectType.class, queryScope.getType().getClass()); 146 | 147 | GraphQLObjectType queryObject = (GraphQLObjectType) queryScope.getType(); 148 | GraphQLFieldDefinition query1Field = queryObject.getFieldDefinition("query1"); 149 | assertNotNull(query1Field); 150 | assertNotNull(query1Field.getArgument("param1")); 151 | assertEquals(Scalars.GraphQLString, query1Field.getArgument("param1").getType()); 152 | 153 | // validate mutation scope 154 | GraphQLFieldDefinition mutationScope = schema.getMutationType().getFieldDefinition("MutationsScope"); 155 | assertNotNull(mutationScope); 156 | assertEquals(GraphQLObjectType.class, mutationScope.getType().getClass()); 157 | GraphQLObjectType mutationObject = (GraphQLObjectType) mutationScope.getType(); 158 | GraphQLFieldDefinition mutationField = mutationObject.getFieldDefinition("mutation1"); 159 | assertNotNull(mutationField); 160 | assertNotNull(mutationField.getArgument("param1")); 161 | assertEquals(Scalars.GraphQLString, mutationField.getArgument("param1").getType()); 162 | } 163 | 164 | public static class AppendingTypeconverter extends DefaultTypeConverter { 165 | 166 | public AppendingTypeconverter(DataFetcher datafetcher) { 167 | super(datafetcher); 168 | } 169 | 170 | @Override 171 | public Object convert(Object value) { 172 | return "prepend:" + value.toString(); 173 | } 174 | } 175 | 176 | @GraphQLController 177 | public class TypeConverterTest { 178 | 179 | @GraphQLTypeConverter(typeConverter = AppendingTypeconverter.class) 180 | @GraphQLQuery(name="someStrings") 181 | public String getSomeStrings() { 182 | return "1"; 183 | } 184 | 185 | } 186 | 187 | @SuppressWarnings("unchecked") 188 | @Test 189 | public void testTypeConverterDetection() { 190 | GraphQLSchema schema = GraphQLSchemaBuilder.newBuilder() 191 | .registerTypeFactory(new JacksonTypeFactory(new ObjectMapper())) 192 | .registerGraphQLControllerObjects(ImmutableList. of(new TypeConverterTest())) 193 | .build(); 194 | 195 | assertEquals("prepend:1", ((Map)new GraphQL(schema).execute("{ someStrings }").getData()).get("someStrings")); 196 | } 197 | 198 | @GraphQLController(rootQueriesObjectName = "Queries", rootMutationsObjectName = "Mutations", 199 | queryDescription="Query Description", mutationDescription="Mutation Description") 200 | public class DocumentedMethodsTest { 201 | 202 | @GraphQLQuery(name="getSomeStrings") 203 | @GraphQLDescription("getSomeStrings description") 204 | public String getSomeStrings() { 205 | return "1"; 206 | } 207 | 208 | @GraphQLMutation(name="setSomeStrings") 209 | @GraphQLDescription("setSomeStrings description") 210 | public String setSomeStrings(@GraphQLParam(name = "name") 211 | @GraphQLDescription("setSomeStrings param name description") 212 | String name) { 213 | return "1"; 214 | } 215 | } 216 | 217 | @SuppressWarnings("unchecked") 218 | @Test 219 | public void testDocumentedControllerAndMethods() { 220 | GraphQLSchema schema = GraphQLSchemaBuilder.newBuilder() 221 | .registerTypeFactory(new JacksonTypeFactory(new ObjectMapper())) 222 | .registerGraphQLControllerObjects(ImmutableList.of(new DocumentedMethodsTest())) 223 | .build(); 224 | 225 | GraphQLObjectType mutationType = (GraphQLObjectType) schema.getMutationType().getFieldDefinition("Mutations").getType(); 226 | GraphQLObjectType queryType = (GraphQLObjectType) schema.getQueryType().getFieldDefinition("Queries").getType();; 227 | assertEquals("Mutation Description", mutationType.getDescription()); 228 | assertEquals("Query Description", queryType.getDescription()); 229 | 230 | GraphQLFieldDefinition someQuery = queryType.getFieldDefinition("getSomeStrings"); 231 | GraphQLFieldDefinition someMutation = mutationType.getFieldDefinition("setSomeStrings"); 232 | assertEquals("getSomeStrings description", someQuery.getDescription()); 233 | assertEquals("setSomeStrings description", someMutation.getDescription()); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/impl/RelayTypeNamingStrategyTest.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.ITypeNamingStrategy; 5 | import com.bretpatterson.schemagen.graphql.relay.RelayConnection; 6 | import com.google.common.reflect.TypeToken; 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.mockito.BDDMockito.given; 11 | import static org.mockito.Matchers.eq; 12 | import static org.mockito.Mockito.mock; 13 | 14 | /** 15 | * Created by bpatterson on 2/10/16. 16 | */ 17 | public class RelayTypeNamingStrategyTest { 18 | IGraphQLObjectMapper graphQLObjectMapper = mock(IGraphQLObjectMapper.class); 19 | 20 | private class Connection { 21 | @SuppressWarnings("unused") 22 | T field; 23 | } 24 | 25 | @SuppressWarnings({ "serial", "unchecked", "rawtypes" }) 26 | @Test 27 | public void testRelayNamingTypes() { 28 | ITypeNamingStrategy strategy = new RelayTypeNamingStrategy(); 29 | 30 | 31 | given(graphQLObjectMapper.getClassFromType(eq(String.class))).willReturn((Class) String.class); 32 | 33 | assertEquals("String", strategy.getTypeName(graphQLObjectMapper, String.class)); 34 | 35 | given(graphQLObjectMapper.getClassFromType(eq(new TypeToken>(){}.getType()))).willReturn((Class) RelayConnection.class); 36 | assertEquals("Relay_String_Connection", strategy.getTypeName(graphQLObjectMapper, new TypeToken>(){}.getType())); 37 | 38 | given(graphQLObjectMapper.getClassFromType(eq(new TypeToken>(){}.getType()))).willReturn((Class) Connection.class); 39 | assertEquals("String_Connection", strategy.getTypeName(graphQLObjectMapper, new TypeToken>(){}.getType())); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/impl/SimpleTypeNamingStrategyTest.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.ITypeNamingStrategy; 5 | import com.bretpatterson.schemagen.graphql.relay.RelayConnection; 6 | import com.google.common.reflect.TypeToken; 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.mockito.BDDMockito.given; 11 | import static org.mockito.Matchers.eq; 12 | import static org.mockito.Mockito.mock; 13 | 14 | /** 15 | * Created by bpatterson on 2/10/16. 16 | */ 17 | public class SimpleTypeNamingStrategyTest { 18 | IGraphQLObjectMapper graphQLObjectMapper = mock(IGraphQLObjectMapper.class); 19 | 20 | private class Connection { 21 | @SuppressWarnings("unused") 22 | T field; 23 | } 24 | 25 | @SuppressWarnings({ "unchecked", "serial", "rawtypes" }) 26 | @Test 27 | public void TestSimpleType() { 28 | ITypeNamingStrategy strategy = new SimpleTypeNamingStrategy(); 29 | 30 | 31 | given(graphQLObjectMapper.getClassFromType(eq(String.class))).willReturn((Class) String.class); 32 | 33 | assertEquals("String", strategy.getTypeName(graphQLObjectMapper, String.class)); 34 | 35 | given(graphQLObjectMapper.getClassFromType(eq(new TypeToken>(){}.getType()))).willReturn((Class) RelayConnection.class); 36 | assertEquals("RelayConnection_String", strategy.getTypeName(graphQLObjectMapper, new TypeToken>(){}.getType())); 37 | 38 | given(graphQLObjectMapper.getClassFromType(eq(new TypeToken>(){}.getType()))).willReturn((Class) Connection.class); 39 | assertEquals("Connection_String", strategy.getTypeName(graphQLObjectMapper, new TypeToken>(){}.getType())); 40 | } 41 | 42 | @SuppressWarnings({ "unchecked", "serial", "rawtypes" }) 43 | @Test 44 | public void TestConfigureDelimiterAndInputPostfix() { 45 | ITypeNamingStrategy strategy = new SimpleTypeNamingStrategy("$", "GraphQLInput"); 46 | 47 | 48 | given(graphQLObjectMapper.getClassFromType(eq(String.class))).willReturn((Class) String.class); 49 | 50 | assertEquals("String", strategy.getTypeName(graphQLObjectMapper, String.class)); 51 | 52 | given(graphQLObjectMapper.getClassFromType(eq(new TypeToken>(){}.getType()))).willReturn((Class) RelayConnection.class); 53 | assertEquals("RelayConnection$String", strategy.getTypeName(graphQLObjectMapper, new TypeToken>(){}.getType())); 54 | 55 | given(graphQLObjectMapper.getClassFromType(eq(new TypeToken>(){}.getType()))).willReturn((Class) Connection.class); 56 | assertEquals("Connection$String", strategy.getTypeName(graphQLObjectMapper, new TypeToken>(){}.getType())); 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/impl/common/JacksonTypeFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.impl.common; 2 | 3 | import com.bretpatterson.schemagen.graphql.ITypeFactory; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.type.TypeFactory; 6 | import com.google.common.base.Throwables; 7 | 8 | import java.io.IOException; 9 | import java.lang.reflect.Type; 10 | 11 | /** 12 | * A very simple object mapper that uses Jackson JSon serialization 13 | */ 14 | public class JacksonTypeFactory implements ITypeFactory { 15 | ObjectMapper objectMapper; 16 | 17 | public JacksonTypeFactory(ObjectMapper objectMapper) { 18 | this.objectMapper = objectMapper; 19 | } 20 | 21 | @Override 22 | public Object convertToType(Type type, Object arg) { 23 | try { 24 | return objectMapper.readValue(objectMapper.writeValueAsString(arg), TypeFactory.defaultInstance().constructType(type)); 25 | } catch(IOException ex) { 26 | return Throwables.propagate(ex); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/IGameFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.model.IGame; 4 | 5 | /** 6 | * Created by bpatterson on 1/30/16. 7 | */ 8 | public interface IGameFactory { 9 | 10 | public IGame newGame(); 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/IUserFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.model.IUser; 4 | 5 | /** 6 | * Created by bpatterson on 1/30/16. 7 | */ 8 | public interface IUserFactory { 9 | 10 | IUser newUser(); 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/RelayTest.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay; 2 | 3 | import com.bretpatterson.schemagen.graphql.GraphQLSchemaBuilder; 4 | import com.bretpatterson.schemagen.graphql.impl.common.JacksonTypeFactory; 5 | import com.bretpatterson.schemagen.graphql.relay.controller.GameController; 6 | import com.bretpatterson.schemagen.graphql.relay.controller.GameDTO; 7 | import com.bretpatterson.schemagen.graphql.relay.controller.UserDTO; 8 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.DeleteGamePayload; 9 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.DeleteUserPayload; 10 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.NewGameInput; 11 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.NewGamePayload; 12 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.NewUserPayload; 13 | import com.bretpatterson.schemagen.graphql.relay.model.relay.factories.RelayGameFactory; 14 | import com.bretpatterson.schemagen.graphql.relay.model.relay.factories.RelayUserFactory; 15 | import com.fasterxml.jackson.core.type.TypeReference; 16 | import com.fasterxml.jackson.databind.ObjectMapper; 17 | import com.google.common.base.Throwables; 18 | import com.google.common.collect.ImmutableList; 19 | import graphql.ExecutionResult; 20 | import graphql.GraphQL; 21 | import graphql.schema.GraphQLSchema; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | 25 | import java.io.IOException; 26 | import java.util.Map; 27 | 28 | import static junit.framework.TestCase.assertTrue; 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertNull; 31 | 32 | /** 33 | * Created by bpatterson on 1/27/16. 34 | */ 35 | public class RelayTest { 36 | 37 | GraphQLSchema schema; 38 | ObjectMapper objectMapper = new ObjectMapper(); 39 | GameController gameController = new GameController(); 40 | 41 | @Before 42 | public void setup() { 43 | this.schema = GraphQLSchemaBuilder.newBuilder() 44 | .registerGraphQLControllerObjects(ImmutableList. of(gameController)) 45 | .registerTypeFactory(new JacksonTypeFactory(new ObjectMapper())) 46 | .registerNodeFactories(ImmutableList.of(new RelayGameFactory(gameController), new RelayUserFactory(gameController))) 47 | .relayEnabled(true) 48 | .build(); 49 | 50 | } 51 | 52 | @SuppressWarnings("unchecked") 53 | private Map getQueryResults(ExecutionResult result, String queryName) { 54 | Map results = (Map) ((Map) result.getData()).get("Queries"); 55 | 56 | return (Map) results.get(queryName); 57 | } 58 | 59 | @SuppressWarnings("unchecked") 60 | private Map getMutationResults(ExecutionResult result, String mutationName) { 61 | Map results = (Map) ((Map) result.getData()).get("Mutations"); 62 | 63 | return (Map) results.get(mutationName); 64 | } 65 | 66 | private NewGamePayload createGame(String name, String clientMutationId) throws IOException { 67 | NewGameInput input = objectMapper.readValue(String.format("{\"game\":{\"name\":\"%s\"}, \"clientMutationId\":\"%s\" }", name, clientMutationId), 68 | NewGameInput.class); 69 | ExecutionResult result = new GraphQL(schema).execute(String.format( 70 | "mutation M { Mutations { createGame(input:{ game: {name:\"%s\"}, clientMutationId:\"%s\"}) { game {id, name}, clientMutationId } } }", 71 | name, 72 | clientMutationId)); 73 | assertEquals(0, result.getErrors().size()); 74 | 75 | NewGamePayload payload = objectMapper.readValue(objectMapper.writeValueAsString(getMutationResults(result, "createGame")), NewGamePayload.class); 76 | 77 | assertEquals(name, payload.getGame().getName()); 78 | assertEquals(clientMutationId, payload.getClientMutationId()); 79 | 80 | return payload; 81 | } 82 | 83 | private DeleteGamePayload deleteGame(String id, String clientMutationId) throws IOException { 84 | ExecutionResult result = new GraphQL(schema).execute( 85 | String.format("mutation M { Mutations {deleteGame(input:{ id:\"%s\", clientMutationId:\"%s\"}) {clientMutationId} } }", id, clientMutationId)); 86 | assertEquals(0, result.getErrors().size()); 87 | 88 | DeleteGamePayload payload = objectMapper.readValue(objectMapper.writeValueAsString(getMutationResults(result, "deleteGame")), DeleteGamePayload.class); 89 | 90 | assertEquals(clientMutationId, payload.getClientMutationId()); 91 | 92 | return payload; 93 | } 94 | 95 | private NewUserPayload createUser(String name, String email, String clientMutationId) throws IOException { 96 | ExecutionResult result = new GraphQL(schema).execute(String.format( 97 | "mutation M { Mutations {createUser(input: { user: { name:\"%s\", email:\"%s\"}, clientMutationId:\"%s\"}) { user {id, name, email}, clientMutationId} } }", 98 | name, 99 | email, 100 | clientMutationId)); 101 | assertEquals(0, result.getErrors().size()); 102 | 103 | NewUserPayload payload = objectMapper.readValue(objectMapper.writeValueAsString(getMutationResults(result, "createUser")), NewUserPayload.class); 104 | 105 | assertEquals(name, payload.getUser().getName()); 106 | assertEquals(email, payload.getUser().getEmail()); 107 | assertEquals(clientMutationId, payload.getClientMutationId()); 108 | 109 | return payload; 110 | } 111 | 112 | private DeleteUserPayload deleteUser(String id, String clientMutationId) throws IOException { 113 | ExecutionResult result = new GraphQL(schema).execute(String 114 | .format("mutation M { Mutations { deleteUser(input: { id:\"%s\", clientMutationId:\"%s\"}) {clientMutationId} } }", id, clientMutationId)); 115 | assertEquals(0, result.getErrors().size()); 116 | 117 | DeleteUserPayload payload = objectMapper.readValue(objectMapper.writeValueAsString(getMutationResults(result, "deleteUser")), DeleteUserPayload.class); 118 | 119 | assertEquals(clientMutationId, payload.getClientMutationId()); 120 | 121 | return payload; 122 | } 123 | 124 | @SuppressWarnings("rawtypes") 125 | private GameDTO getGameNode(String id) throws IOException { 126 | 127 | ExecutionResult result = new GraphQL(schema).execute(String.format("{ node(id:\"%s\") {...on Game {id, name} } }", id)); 128 | assertEquals(0, result.getErrors().size()); 129 | 130 | return objectMapper.readValue(objectMapper.writeValueAsString(((Map) result.getData()).get("node")), GameDTO.class); 131 | } 132 | 133 | @SuppressWarnings("rawtypes") 134 | private UserDTO getUserNode(String id) throws IOException { 135 | 136 | ExecutionResult result = new GraphQL(schema).execute(String.format("{ node(id:\"%s\") {...on User {id, name, email} } }", id)); 137 | assertEquals(0, result.getErrors().size()); 138 | 139 | return objectMapper.readValue(objectMapper.writeValueAsString(((Map) result.getData()).get("node")), UserDTO.class); 140 | } 141 | 142 | private RelayConnection findGames(int first) throws IOException { 143 | 144 | ExecutionResult result = new GraphQL(schema).execute(String 145 | .format("{ Queries { games(first:%d) { edges { node {id, name}, cursor } , pageInfo {hasPreviousPage, hasNextPage} } } }", first)); 146 | assertEquals(result.getErrors().toString(), 0, result.getErrors().size()); 147 | 148 | return deserialize(objectMapper.writeValueAsString(getQueryResults(result, "games")), new TypeReference>() { 149 | }); 150 | } 151 | 152 | private T deserialize(String value, TypeReference type) { 153 | try { 154 | return objectMapper.readValue(value, type); 155 | } catch (IOException ex) { 156 | throw Throwables.propagate(ex); 157 | } 158 | } 159 | 160 | @Test 161 | public void testCreateGame() throws IOException { 162 | GameDTO newGame; 163 | GameDTO game; 164 | 165 | newGame = createGame("Game1", "Game1").getGame(); 166 | game = getGameNode(newGame.getId()); 167 | assertEquals(newGame.getId(), game.getId()); 168 | assertEquals(newGame.getName(), game.getName()); 169 | } 170 | 171 | @Test 172 | public void testCreateUser() throws IOException { 173 | UserDTO newUser = createUser("User1", "user1@gmail.com", "user1").getUser(); 174 | UserDTO user = getUserNode(newUser.getId()); 175 | 176 | assertEquals(newUser.getId(), user.getId()); 177 | assertEquals(newUser.getName(), user.getName()); 178 | assertEquals(newUser.getEmail(), user.getEmail()); 179 | } 180 | 181 | @Test 182 | public void testDeleteGame() throws IOException { 183 | GameDTO newGame; 184 | GameDTO game; 185 | 186 | newGame = createGame("Game1", "Game1").getGame(); 187 | game = getGameNode(newGame.getId()); 188 | assertEquals(newGame.getId(), game.getId()); 189 | assertEquals(newGame.getName(), game.getName()); 190 | 191 | deleteGame(newGame.getId(), "deletGame"); 192 | game = getGameNode(newGame.getId()); 193 | assertNull(game); 194 | } 195 | 196 | @Test 197 | public void testDeleteUser() throws IOException { 198 | UserDTO newUser = createUser("User1", "user1@gmail.com", "user1").getUser(); 199 | UserDTO user = getUserNode(newUser.getId()); 200 | 201 | assertEquals(newUser.getId(), user.getId()); 202 | assertEquals(newUser.getName(), user.getName()); 203 | assertEquals(newUser.getEmail(), user.getEmail()); 204 | 205 | deleteUser(newUser.getId(), "deleteUser"); 206 | user = getUserNode(newUser.getId()); 207 | assertNull(user); 208 | } 209 | 210 | @Test 211 | public void testFindGames() throws IOException { 212 | GameDTO newGame; 213 | 214 | for (int i = 0; i < 1000; i++) { 215 | newGame = createGame(String.format("Game %d", i), String.format("Mutation ID:%d", i)).getGame(); 216 | } 217 | RelayConnection games = findGames(100); 218 | 219 | assertEquals(100, games.getEdges().size()); 220 | assertTrue(games.getPageInfo().isHasNextPage()); 221 | assertTrue(!games.getPageInfo().isHasPreviousPage()); 222 | for (int i = 0; i < 100; i++) { 223 | assertEquals(String.format("Game %d", i), games.getEdges().get(i).getNode().getName()); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/controller/GameController.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.controller; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLController; 4 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLMutation; 5 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLParam; 6 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLQuery; 7 | import com.bretpatterson.schemagen.graphql.relay.ConnectionCursor; 8 | import com.bretpatterson.schemagen.graphql.relay.Edge; 9 | import com.bretpatterson.schemagen.graphql.relay.PageInfo; 10 | import com.bretpatterson.schemagen.graphql.relay.RelayConnection; 11 | import com.bretpatterson.schemagen.graphql.relay.manager.GameManager; 12 | import com.bretpatterson.schemagen.graphql.relay.model.IGame; 13 | import com.bretpatterson.schemagen.graphql.relay.model.IUser; 14 | import com.bretpatterson.schemagen.graphql.relay.model.PagedList; 15 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.AddUserToGameInput; 16 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.AddUserToGamePayload; 17 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.DeleteGameInput; 18 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.DeleteGamePayload; 19 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.DeleteUserInput; 20 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.DeleteUserPayload; 21 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.NewGameInput; 22 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.NewGamePayload; 23 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.NewUserInput; 24 | import com.bretpatterson.schemagen.graphql.relay.model.payloads.NewUserPayload; 25 | import com.bretpatterson.schemagen.graphql.relay.model.relay.factories.RelayGameFactory; 26 | import com.bretpatterson.schemagen.graphql.relay.model.relay.factories.RelayUserFactory; 27 | import com.google.common.base.Function; 28 | import com.google.common.base.Optional; 29 | import com.google.common.collect.ImmutableList; 30 | import com.google.common.collect.Lists; 31 | 32 | import java.util.List; 33 | 34 | import static com.google.common.base.Preconditions.checkNotNull; 35 | 36 | /** 37 | * Created by bpatterson on 1/27/16. 38 | */ 39 | @GraphQLController(rootQueriesObjectName = "Queries", rootMutationsObjectName = "Mutations") 40 | public class GameController { 41 | 42 | GameManager gameManager = new GameManager(); 43 | /** 44 | * our game factories used by relay 45 | */ 46 | RelayGameFactory gameFactory = new RelayGameFactory(this); 47 | RelayUserFactory userFactory = new RelayUserFactory(this); 48 | 49 | @GraphQLQuery(name = "games") 50 | public RelayConnection findGames(@GraphQLParam(name = "first") Integer first, 51 | @GraphQLParam(name = "last") Integer last, 52 | @GraphQLParam(name = "before") ConnectionCursor beforeCursor, 53 | @GraphQLParam(name = "after") ConnectionCursor afterCursor) { 54 | Optional before = Optional.absent(); 55 | Optional after = Optional.absent(); 56 | if (beforeCursor != null) { 57 | before = Optional.of(Long.parseLong(beforeCursor.getValue())); 58 | } 59 | if (afterCursor != null) { 60 | after = Optional.of(Long.parseLong(afterCursor.getValue())); 61 | } 62 | PagedList games = gameManager.findGames(Optional.fromNullable(first), Optional.fromNullable(last), before, after); 63 | 64 | return convertToGameConnection(games); 65 | } 66 | 67 | @GraphQLMutation(name = "addUserToGame") 68 | public AddUserToGamePayload addUserToGame(@GraphQLParam(name = "input") AddUserToGameInput input) { 69 | 70 | Optional foundGame = gameManager.findGame(gameFactory.getGameId(input.getGameId())); 71 | Optional foundUser = gameManager.findUser(userFactory.getUserId(input.getUserId())); 72 | 73 | if (foundGame.isPresent() && foundUser.isPresent()) { 74 | IGame game = foundGame.get(); 75 | IUser user = foundUser.get(); 76 | game.getUsers().add(user); 77 | game = gameManager.updateGame(game); 78 | GameDTO gameDTO = convertToGameDTO(game); 79 | 80 | return new AddUserToGamePayload().setGame(gameDTO).setClientMutationId(input.getClientMutationId()); 81 | } else { 82 | return new AddUserToGamePayload().setClientMutationId(input.getClientMutationId()).setGame(null); 83 | } 84 | 85 | } 86 | 87 | @GraphQLQuery(name = "users") 88 | public RelayConnection findUsers(@GraphQLParam(name = "first") Integer first, 89 | @GraphQLParam(name = "first") Integer last, 90 | @GraphQLParam(name = "before") ConnectionCursor beforeCursor, 91 | @GraphQLParam(name = "after") ConnectionCursor afterCursor) { 92 | Optional before = Optional.absent(); 93 | Optional after = Optional.absent(); 94 | if (beforeCursor != null) { 95 | before = Optional.of(Long.parseLong(beforeCursor.getValue())); 96 | } 97 | if (afterCursor != null) { 98 | after = Optional.of(Long.parseLong(afterCursor.getValue())); 99 | } 100 | PagedList users = gameManager.findUsers(Optional.fromNullable(first), Optional.fromNullable(last), before, after); 101 | 102 | return convertToUserConnection(users); 103 | } 104 | 105 | @GraphQLMutation(name = "createGame") 106 | public NewGamePayload createGame(@GraphQLParam(name = "input", required = true) NewGameInput newGameInput) { 107 | checkNotNull(newGameInput.getGame()); 108 | checkNotNull(newGameInput.getGame().getName()); 109 | checkNotNull(newGameInput.getClientMutationId()); 110 | IGame newGame = gameManager.newGame().setName(newGameInput.getGame().getName()); 111 | 112 | newGame = gameManager.createGame(newGame); 113 | 114 | return new NewGamePayload(convertToGameDTO(newGame), newGameInput.getClientMutationId()); 115 | } 116 | 117 | @GraphQLMutation(name = "deleteGame") 118 | public DeleteGamePayload deleteGame(@GraphQLParam(name = "input", required = true) DeleteGameInput game) { 119 | checkNotNull(game); 120 | checkNotNull(game.getId()); 121 | checkNotNull(game.getClientMutationId()); 122 | gameManager.deleteGame(gameFactory.getGameId(game.getId())); 123 | 124 | return new DeleteGamePayload(game.getClientMutationId()); 125 | 126 | } 127 | 128 | @GraphQLMutation(name = "createUser") 129 | public NewUserPayload createUser(@GraphQLParam(name = "input", required = true) NewUserInput userInput) { 130 | checkNotNull(userInput); 131 | checkNotNull(userInput.getUser()); 132 | checkNotNull(userInput.getUser().getEmail()); 133 | checkNotNull(userInput.getUser().getName()); 134 | checkNotNull(userInput.getClientMutationId()); 135 | 136 | IUser user = gameManager.newUser().setName(userInput.getUser().getName()).setEmail(userInput.getUser().getEmail()); 137 | IUser newUser = gameManager.createUser(user); 138 | 139 | return new NewUserPayload(convertToUserDTO(newUser), userInput.getClientMutationId()); 140 | } 141 | 142 | @GraphQLMutation(name = "deleteUser") 143 | public DeleteUserPayload deleteUser(@GraphQLParam(name = "input", required = true) DeleteUserInput userInput) { 144 | checkNotNull(userInput); 145 | checkNotNull(userInput.getId()); 146 | checkNotNull(userInput.getClientMutationId()); 147 | gameManager.deleteUser(userFactory.getUserId(userInput.getId())); 148 | 149 | return new DeleteUserPayload(userInput.getClientMutationId()); 150 | } 151 | 152 | public GameDTO findGame(Long id) { 153 | return convertToGameDTO(gameManager.findGame(id).orNull()); 154 | } 155 | 156 | public UserDTO findUser(Long id) { 157 | return convertToUserDTO(gameManager.findUser(id).orNull()); 158 | } 159 | 160 | private GameDTO convertToGameDTO(IGame game) { 161 | if (game == null) { 162 | return null; 163 | } 164 | return new GameDTO() 165 | .setId(gameFactory.getNodeId(game.getId())) 166 | .setName(game.getName()) 167 | .setUsers(convertToUserDTOList(game.getUsers())); 168 | } 169 | 170 | private List convertToUserDTOList(List user) { 171 | return Lists.transform(user, new Function() { 172 | @Override 173 | public UserDTO apply(IUser input) { 174 | return convertToUserDTO(input); 175 | } 176 | }); 177 | } 178 | 179 | private UserDTO convertToUserDTO(IUser user) { 180 | if (user == null) { 181 | return null; 182 | } 183 | UserDTO rv = new UserDTO().setId(userFactory.getNodeId(user.getId())).setName(user.getName()).setEmail(user.getEmail()); 184 | 185 | return rv; 186 | } 187 | 188 | private RelayConnection convertToUserConnection(PagedList users) { 189 | RelayConnection rv = new RelayConnection<>(); 190 | 191 | PageInfo pageInfo = new PageInfo(); 192 | pageInfo.setHasNextPage(users.isHasNextPage()); 193 | pageInfo.setHasPreviousPage(users.isHasPreviousPage()); 194 | rv.setPageInfo(pageInfo); 195 | ImmutableList.Builder> edges = ImmutableList.builder(); 196 | 197 | for (IUser user : users.getItems()) { 198 | edges.add(new Edge<>(convertToUserDTO(user), new ConnectionCursor(user.getId().toString()))); 199 | } 200 | 201 | rv.setEdges(edges.build()); 202 | 203 | return rv; 204 | 205 | } 206 | 207 | private RelayConnection convertToGameConnection(PagedList games) { 208 | RelayConnection rv = new RelayConnection<>(); 209 | 210 | PageInfo pageInfo = new PageInfo(); 211 | pageInfo.setHasNextPage(games.isHasNextPage()); 212 | pageInfo.setHasPreviousPage(games.isHasPreviousPage()); 213 | rv.setPageInfo(pageInfo); 214 | ImmutableList.Builder> edges = ImmutableList.builder(); 215 | 216 | for (IGame game : games.getItems()) { 217 | edges.add(new Edge<>(convertToGameDTO(game), new ConnectionCursor(game.getId().toString()))); 218 | } 219 | 220 | rv.setEdges(edges.build()); 221 | 222 | return rv; 223 | 224 | } 225 | 226 | private int getPageNumber(ConnectionCursor cursor) { 227 | return Integer.parseInt(cursor.getValue()); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/controller/GameDTO.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.controller; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLName; 4 | import com.bretpatterson.schemagen.graphql.relay.INode; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by bpatterson on 1/30/16. 10 | */ 11 | @GraphQLName(name="Game") 12 | public class GameDTO implements INode { 13 | private String id; 14 | private String name; 15 | private List users; 16 | 17 | @Override 18 | public String getId() { 19 | return id; 20 | } 21 | 22 | public GameDTO setId(String id) { 23 | this.id = id; 24 | 25 | return this; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public GameDTO setName(String name) { 33 | this.name = name; 34 | return this; 35 | } 36 | 37 | public List getUsers() { 38 | return users; 39 | } 40 | 41 | public GameDTO setUsers(List users) { 42 | this.users = users; 43 | 44 | return this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/controller/UserDTO.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.controller; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.INode; 4 | 5 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLName; 6 | 7 | /** 8 | * Created by bpatterson on 1/30/16. 9 | */ 10 | @GraphQLName(name="User") 11 | public class UserDTO implements INode { 12 | private String id; 13 | private String name; 14 | private String email; 15 | 16 | 17 | @Override 18 | public String getId() { 19 | return id; 20 | } 21 | 22 | public UserDTO setId(String id) { 23 | this.id = id; 24 | 25 | return this; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public UserDTO setName(String name) { 33 | this.name = name; 34 | 35 | return this; 36 | } 37 | 38 | public String getEmail() { 39 | return email; 40 | } 41 | 42 | public UserDTO setEmail(String email) { 43 | this.email = email; 44 | 45 | return this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/dao/GameDAO.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.dao; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.model.IGame; 4 | import com.bretpatterson.schemagen.graphql.relay.model.IUser; 5 | import com.bretpatterson.schemagen.graphql.relay.model.PagedList; 6 | import com.google.common.base.Optional; 7 | import com.google.common.collect.ImmutableList; 8 | import com.google.common.collect.Lists; 9 | 10 | import java.util.Iterator; 11 | import java.util.List; 12 | 13 | import static com.google.common.base.Preconditions.checkArgument; 14 | 15 | /** 16 | * Simple in memory story for all game objects 17 | */ 18 | public class GameDAO { 19 | 20 | private List games = Lists.newArrayList(); 21 | private List users = Lists.newArrayList(); 22 | private long lastGameId = 0; 23 | private long lastUserId = 0; 24 | 25 | public PagedList findGames(Optional first, Optional last, Optional before, Optional after) { 26 | checkArgument(!before.isPresent() || (before.isPresent() && before.get() >= 0)); 27 | checkArgument(!before.isPresent() || before.isPresent() && before.get() < games.size()); 28 | checkArgument(!after.isPresent() || after.isPresent() && after.get() >= 0); 29 | checkArgument(!after.isPresent() || after.isPresent() && after.get() < games.size()); 30 | 31 | List items; 32 | int start = 0, end = games.size(); 33 | if (before.isPresent()) { 34 | end = indexOfGame(before.get()); 35 | } 36 | if (after.isPresent()) { 37 | start = indexOfGame(after.get()); 38 | } 39 | 40 | if (first.isPresent() && (end - start) > first.get()) { 41 | // move end to set the size to first 42 | end = end - (end - start - first.get()); 43 | } 44 | if (last.isPresent() && (end - start) > last.get()) { 45 | start = start + (end - start - last.get()); 46 | } 47 | items = games.subList(start, end); 48 | 49 | return PagedList.of(items, start > 0, end < (games.size() - 1)); 50 | 51 | } 52 | 53 | public PagedList findUsers(Optional first, Optional last, Optional before, Optional after) { 54 | checkArgument(before.isPresent() && before.get() >= 0); 55 | checkArgument(before.isPresent() && before.get() < users.size()); 56 | checkArgument(after.isPresent() && after.get() >= 0); 57 | checkArgument(after.isPresent() && after.get() < users.size()); 58 | checkArgument(before.isPresent() || after.isPresent()); 59 | 60 | List items; 61 | int start = 0, end = users.size(); 62 | if (before.isPresent()) { 63 | end = indexOfUser(before.get()); 64 | } 65 | if (after.isPresent()) { 66 | start = indexOfUser(after.get()); 67 | } 68 | 69 | if (first.isPresent() && (end - start) > first.get()) { 70 | // move end to set the size to first 71 | end = end - (end - start - first.get()); 72 | } 73 | if (last.isPresent() && (end - start) > last.get()) { 74 | start = start + (end - start - last.get()); 75 | } 76 | items = users.subList(start, end); 77 | 78 | return PagedList.of(items, start > 0, end < (users.size() - 1)); 79 | } 80 | 81 | public Optional findGame(Long id) { 82 | for (IGame game : games) { 83 | if (id.equals(game.getId())) { 84 | return Optional.of(game); 85 | } 86 | } 87 | return Optional.absent(); 88 | } 89 | 90 | public Optional findUser(Long id) { 91 | for (IUser user : users) { 92 | if (id.equals(user.getId())) { 93 | return Optional.of(user); 94 | } 95 | } 96 | return Optional.absent(); 97 | } 98 | 99 | public IGame saveGame(IGame game) { 100 | if (game.getId() == null) { 101 | game.setId(++lastGameId); 102 | } 103 | games.add(game); 104 | return game; 105 | } 106 | 107 | public IUser saveUser(IUser user) { 108 | if (user.getId() == null) { 109 | user.setId(++lastUserId); 110 | } 111 | users.add(user); 112 | return user; 113 | } 114 | 115 | public void removeGame(Long id) { 116 | Optional gameToDelete = findGame(id); 117 | if (!gameToDelete.isPresent()) { 118 | return; 119 | } 120 | games.remove(gameToDelete.get()); 121 | } 122 | 123 | public void removeUser(Long id) { 124 | Optional userToDelete = findUser(id); 125 | if (!userToDelete.isPresent()) { 126 | return; 127 | } 128 | users.remove(userToDelete.get()); 129 | for (IGame game : games) { 130 | Iterator iter = game.getUsers().iterator(); 131 | while (iter.hasNext()) { 132 | IUser user = iter.next(); 133 | if (id.equals(user.getId())) { 134 | iter.remove(); 135 | break; 136 | } 137 | } 138 | } 139 | } 140 | 141 | public List getAllGames() { 142 | return ImmutableList.copyOf(games); 143 | } 144 | 145 | public List getAllUsers() { 146 | return ImmutableList.copyOf(users); 147 | } 148 | 149 | public int indexOfGame(Long gameId) { 150 | int i = 0; 151 | for (IGame item : games) { 152 | if (gameId.equals(item.getId())) { 153 | return i; 154 | } 155 | i++; 156 | } 157 | 158 | throw new IllegalArgumentException(String.format("Game id %d not found.", gameId)); 159 | } 160 | 161 | public int indexOfUser(Long userId) { 162 | int i = 0; 163 | for (IUser item : users) { 164 | if (userId.equals(item.getId())) { 165 | return i; 166 | } 167 | i++; 168 | } 169 | 170 | throw new IllegalArgumentException(String.format("User id %d not found.", userId)); 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/manager/GameManager.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.manager; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.IGameFactory; 4 | import com.bretpatterson.schemagen.graphql.relay.IUserFactory; 5 | import com.bretpatterson.schemagen.graphql.relay.dao.GameDAO; 6 | import com.bretpatterson.schemagen.graphql.relay.model.IGame; 7 | import com.bretpatterson.schemagen.graphql.relay.model.IUser; 8 | import com.bretpatterson.schemagen.graphql.relay.model.PagedList; 9 | import com.bretpatterson.schemagen.graphql.relay.model.impl.Game; 10 | import com.bretpatterson.schemagen.graphql.relay.model.impl.User; 11 | import com.google.common.base.Optional; 12 | 13 | import static com.google.common.base.Preconditions.checkNotNull; 14 | 15 | /** 16 | * Created by bpatterson on 1/30/16. 17 | */ 18 | public class GameManager implements IGameFactory, IUserFactory { 19 | GameDAO gameDAO = new GameDAO(); 20 | 21 | @Override 22 | public IGame newGame() { 23 | return new Game(); 24 | } 25 | @Override 26 | public IUser newUser() { 27 | return new User(); 28 | } 29 | 30 | public PagedList findGames(Optional first, Optional last, Optional before, Optional after) { 31 | return gameDAO.findGames(first, last, before, after); 32 | } 33 | 34 | public PagedList findUsers(Optional first, Optional last, Optional before, Optional after) { 35 | return gameDAO.findUsers(first, last, before, after); 36 | } 37 | 38 | public IUser createUser(IUser user) { 39 | checkNotNull(user.getName()); 40 | checkNotNull(user.getEmail()); 41 | 42 | return gameDAO.saveUser(user); 43 | } 44 | public IUser updateUser(IUser user) { 45 | checkNotNull(user.getName()); 46 | checkNotNull(user.getEmail()); 47 | 48 | return gameDAO.saveUser(user); 49 | } 50 | public void deleteUser(Long id) { 51 | gameDAO.removeUser(id); 52 | } 53 | 54 | public IGame createGame(IGame game) { 55 | checkNotNull(game.getName()); 56 | 57 | return gameDAO.saveGame(game); 58 | } 59 | public IGame updateGame(IGame game) { 60 | checkNotNull(game.getId()); 61 | checkNotNull(game.getName()); 62 | 63 | return gameDAO.saveGame(game); 64 | } 65 | 66 | public void deleteGame(Long id) { 67 | gameDAO.removeGame(id); 68 | } 69 | 70 | 71 | public Optional findGame(Long id) { 72 | return gameDAO.findGame(id); 73 | } 74 | 75 | public Optional findUser(Long id) { 76 | return gameDAO.findUser(id); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/IGame.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by bpatterson on 1/30/16. 7 | */ 8 | public interface IGame { 9 | 10 | Long getId(); 11 | 12 | IGame setId(Long id); 13 | 14 | String getName(); 15 | 16 | IGame setName(String name); 17 | 18 | List getUsers(); 19 | 20 | IGame setUsers(List users); 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/IUser.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model; 2 | 3 | /** 4 | * Represents a game user 5 | */ 6 | public interface IUser { 7 | 8 | Long getId(); 9 | 10 | IUser setId(Long id); 11 | 12 | String getName(); 13 | 14 | IUser setName(String name); 15 | 16 | String getEmail(); 17 | 18 | IUser setEmail(String email); 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/PagedList.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by bpatterson on 1/30/16. 7 | */ 8 | public class PagedList { 9 | 10 | private List items; 11 | private boolean hasPreviousPage; 12 | private boolean hasNextPage; 13 | 14 | private PagedList(List items, boolean hasPreviousPage, boolean hasNextPage) { 15 | this.items = items; 16 | this.setHasPreviousPage(hasPreviousPage); 17 | this.setHasNextPage(hasNextPage); 18 | } 19 | 20 | public static PagedList of(List items, boolean hasPreviousPage, boolean hasNextPage) { 21 | return new PagedList(items, hasPreviousPage, hasNextPage); 22 | } 23 | 24 | public List getItems() { 25 | return items; 26 | } 27 | 28 | public void setItems(List items) { 29 | this.items = items; 30 | } 31 | 32 | public boolean isHasPreviousPage() { 33 | return hasPreviousPage; 34 | } 35 | 36 | public void setHasPreviousPage(boolean hasPreviousPage) { 37 | this.hasPreviousPage = hasPreviousPage; 38 | } 39 | 40 | public boolean isHasNextPage() { 41 | return hasNextPage; 42 | } 43 | 44 | public void setHasNextPage(boolean hasNextPage) { 45 | this.hasNextPage = hasNextPage; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/impl/Game.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.model.IGame; 4 | import com.bretpatterson.schemagen.graphql.relay.model.IUser; 5 | import com.google.common.collect.Lists; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by bpatterson on 1/27/16. 11 | */ 12 | public class Game implements IGame { 13 | private Long id; 14 | private String name; 15 | private List users; 16 | 17 | public Game() { 18 | this.users = Lists.newArrayList(); 19 | } 20 | 21 | @Override 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | @Override 27 | public IGame setId(Long id) { 28 | this.id = id; 29 | 30 | return this; 31 | } 32 | 33 | @Override 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | @Override 39 | public IGame setName(String name) { 40 | this.name = name; 41 | 42 | return this; 43 | } 44 | 45 | @Override 46 | public List getUsers() { 47 | return users; 48 | } 49 | 50 | @Override 51 | public IGame setUsers(List users) { 52 | this.users = users; 53 | 54 | return this; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/impl/User.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.impl; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.model.IUser; 4 | 5 | /** 6 | * Created by bpatterson on 1/27/16. 7 | */ 8 | public class User implements IUser { 9 | private Long id; 10 | private String name; 11 | private String email; 12 | 13 | @Override 14 | public Long getId() { 15 | return id; 16 | } 17 | 18 | @Override 19 | public IUser setId(Long id) { 20 | this.id = id; 21 | 22 | return this; 23 | } 24 | 25 | @Override 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | @Override 31 | public IUser setName(String name) { 32 | this.name = name; 33 | 34 | return this; 35 | } 36 | 37 | @Override 38 | public String getEmail() { 39 | return email; 40 | } 41 | 42 | @Override 43 | public IUser setEmail(String email) { 44 | this.email = email; 45 | 46 | return this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/AddUserToGameInput.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | /** 4 | * Created by bpatterson on 1/30/16. 5 | */ 6 | public class AddUserToGameInput { 7 | private String gameId; 8 | private String userId; 9 | private String clientMutationId; 10 | 11 | public String getGameId() { 12 | return gameId; 13 | } 14 | 15 | public void setGameId(String gameId) { 16 | this.gameId = gameId; 17 | } 18 | 19 | public String getUserId() { 20 | return userId; 21 | } 22 | 23 | public void setUserId(String userId) { 24 | this.userId = userId; 25 | } 26 | 27 | public String getClientMutationId() { 28 | return clientMutationId; 29 | } 30 | 31 | public void setClientMutationId(String clientMutationId) { 32 | this.clientMutationId = clientMutationId; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/AddUserToGamePayload.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.controller.GameDTO; 4 | 5 | /** 6 | * Created by bpatterson on 1/30/16. 7 | */ 8 | public class AddUserToGamePayload { 9 | private GameDTO game; 10 | private String clientMutationId; 11 | 12 | public GameDTO getGame() { 13 | return game; 14 | } 15 | 16 | public AddUserToGamePayload setGame(GameDTO game) { 17 | this.game = game; 18 | 19 | return this; 20 | } 21 | 22 | public String getClientMutationId() { 23 | return clientMutationId; 24 | } 25 | 26 | public AddUserToGamePayload setClientMutationId(String clientMutationId) { 27 | this.clientMutationId = clientMutationId; 28 | 29 | return this; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/DeleteGameInput.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | /** 4 | * Created by bpatterson on 1/27/16. 5 | */ 6 | public class DeleteGameInput { 7 | private String id; 8 | private String clientMutationId; 9 | 10 | public String getId() { 11 | return id; 12 | } 13 | 14 | public void setId(String gameId) { 15 | this.id = gameId; 16 | } 17 | 18 | public String getClientMutationId() { 19 | return clientMutationId; 20 | } 21 | 22 | public void setClientMutationId(String clientMutationId) { 23 | this.clientMutationId = clientMutationId; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/DeleteGamePayload.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | /** 4 | * Created by bpatterson on 1/27/16. 5 | */ 6 | public class DeleteGamePayload { 7 | private String clientMutationId; 8 | 9 | public DeleteGamePayload() { 10 | 11 | } 12 | 13 | public DeleteGamePayload(String clientMutationId) { 14 | this.clientMutationId = clientMutationId; 15 | } 16 | 17 | public String getClientMutationId() { 18 | return clientMutationId; 19 | } 20 | 21 | public void setClientMutationId(String clientMutationId) { 22 | this.clientMutationId = clientMutationId; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/DeleteUserInput.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | /** 4 | * Created by bpatterson on 1/27/16. 5 | */ 6 | public class DeleteUserInput { 7 | private String id; 8 | private String clientMutationId; 9 | 10 | public String getId() { 11 | return id; 12 | } 13 | 14 | public DeleteUserInput setId(String userId) { 15 | this.id = userId; 16 | 17 | return this; 18 | } 19 | 20 | public String getClientMutationId() { 21 | return clientMutationId; 22 | } 23 | 24 | public DeleteUserInput setClientMutationId(String clientMutationId) { 25 | this.clientMutationId = clientMutationId; 26 | 27 | return this; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/DeleteUserPayload.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | /** 4 | * Created by bpatterson on 1/27/16. 5 | */ 6 | public class DeleteUserPayload { 7 | private String clientMutationId; 8 | 9 | public DeleteUserPayload() { 10 | 11 | } 12 | 13 | public DeleteUserPayload(String clientMutationId) { 14 | this.clientMutationId = clientMutationId; 15 | } 16 | 17 | public String getClientMutationId() { 18 | return clientMutationId; 19 | } 20 | 21 | public void setClientMutationId(String clientMutationId) { 22 | this.clientMutationId = clientMutationId; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/NewGameInput.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.controller.GameDTO; 4 | 5 | /** 6 | * Created by bpatterson on 1/27/16. 7 | */ 8 | public class NewGameInput { 9 | private GameDTO game; 10 | private String clientMutationId; 11 | 12 | public GameDTO getGame() { 13 | return game; 14 | } 15 | 16 | public NewGameInput setGame(GameDTO game) { 17 | this.game = game; 18 | 19 | return this; 20 | } 21 | 22 | public String getClientMutationId() { 23 | return clientMutationId; 24 | } 25 | 26 | public NewGameInput setClientMutationId(String clientMutationId) { 27 | this.clientMutationId = clientMutationId; 28 | 29 | return this; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/NewGamePayload.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.controller.GameDTO; 4 | 5 | /** 6 | * Created by bpatterson on 1/27/16. 7 | */ 8 | public class NewGamePayload { 9 | private GameDTO game; 10 | private String clientMutationId; 11 | 12 | public NewGamePayload() { 13 | 14 | } 15 | 16 | public NewGamePayload(GameDTO game, String clientMutationId) { 17 | this.game = game; 18 | this.clientMutationId = clientMutationId; 19 | } 20 | 21 | public GameDTO getGame() { 22 | return game; 23 | } 24 | 25 | public void setGame(GameDTO game) { 26 | this.game = game; 27 | } 28 | 29 | public String getClientMutationId() { 30 | return clientMutationId; 31 | } 32 | 33 | public void setClientMutationId(String clientMutationId) { 34 | this.clientMutationId = clientMutationId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/NewUserInput.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.controller.UserDTO; 4 | 5 | /** 6 | * Created by bpatterson on 1/27/16. 7 | */ 8 | public class NewUserInput { 9 | private UserDTO user; 10 | private String clientMutationId; 11 | 12 | public UserDTO getUser() { 13 | return user; 14 | } 15 | 16 | public NewUserInput setUser(UserDTO user) { 17 | this.user = user; 18 | 19 | return this; 20 | } 21 | 22 | public String getClientMutationId() { 23 | return clientMutationId; 24 | } 25 | 26 | public NewUserInput setClientMutationId(String clientMutationId) { 27 | this.clientMutationId = clientMutationId; 28 | 29 | return this; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/payloads/NewUserPayload.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.payloads; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.controller.UserDTO; 4 | 5 | /** 6 | * Created by bpatterson on 1/27/16. 7 | */ 8 | public class NewUserPayload { 9 | private UserDTO user; 10 | private String clientMutationId; 11 | 12 | public NewUserPayload() { 13 | 14 | } 15 | 16 | public NewUserPayload(UserDTO user, String clientMutationId) { 17 | this.user = user; 18 | this.clientMutationId = clientMutationId; 19 | } 20 | 21 | public UserDTO getUser() { 22 | return user; 23 | } 24 | 25 | public void setUser(UserDTO user) { 26 | this.user = user; 27 | } 28 | 29 | public String getClientMutationId() { 30 | return clientMutationId; 31 | } 32 | 33 | public void setClientMutationId(String clientMutationId) { 34 | this.clientMutationId = clientMutationId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/relay/factories/RelayGameFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.relay.factories; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.controller.GameController; 4 | import com.bretpatterson.schemagen.graphql.relay.controller.GameDTO; 5 | import com.bretpatterson.schemagen.graphql.relay.model.impl.Game; 6 | import com.bretpatterson.schemagen.graphql.relay.IRelayNodeFactory; 7 | import com.bretpatterson.schemagen.graphql.relay.annotations.RelayNodeFactory; 8 | 9 | /** 10 | * Created by bpatterson on 1/27/16. 11 | */ 12 | @RelayNodeFactory(types={Game.class}) 13 | public class RelayGameFactory implements IRelayNodeFactory { 14 | private static final String NODE_PREFIX = "game:"; 15 | GameController gameController; 16 | 17 | public RelayGameFactory(GameController gameController) { 18 | this.gameController = gameController; 19 | } 20 | 21 | @Override 22 | public boolean handlesNodeId(String id) { 23 | return id.startsWith(NODE_PREFIX); 24 | } 25 | 26 | @Override 27 | public GameDTO newObjectFromID(String objectId) { 28 | return gameController.findGame(getGameId(objectId)); 29 | } 30 | 31 | public String getNodeId(Long id) { 32 | return String.format("%s%d",NODE_PREFIX,id); 33 | } 34 | 35 | public Long getGameId(String id) { 36 | String gameId = id.replace(NODE_PREFIX,""); 37 | 38 | return Long.parseLong(gameId); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/relay/model/relay/factories/RelayUserFactory.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.relay.model.relay.factories; 2 | 3 | import com.bretpatterson.schemagen.graphql.relay.IRelayNodeFactory; 4 | import com.bretpatterson.schemagen.graphql.relay.annotations.RelayNodeFactory; 5 | 6 | import com.bretpatterson.schemagen.graphql.relay.controller.GameController; 7 | import com.bretpatterson.schemagen.graphql.relay.controller.UserDTO; 8 | import com.bretpatterson.schemagen.graphql.relay.model.impl.User; 9 | 10 | /** 11 | * Factory that knows how to turn node ids into User objects 12 | */ 13 | @RelayNodeFactory(types = { User.class }) 14 | public class RelayUserFactory implements IRelayNodeFactory { 15 | private static final String NODE_PREFIX = "user:"; 16 | 17 | GameController gameController; 18 | 19 | public RelayUserFactory(GameController gameController) { 20 | this.gameController = gameController; 21 | } 22 | 23 | @Override 24 | public UserDTO newObjectFromID(String objectId) { 25 | return gameController.findUser(getUserId(objectId)); 26 | } 27 | 28 | @Override 29 | public boolean handlesNodeId(String id) { 30 | return id.startsWith(NODE_PREFIX); 31 | } 32 | 33 | public String getNodeId(Long id) { 34 | return String.format("%s%d", NODE_PREFIX, id); 35 | } 36 | 37 | 38 | public Long getUserId(String id) { 39 | String userId = id.replace(NODE_PREFIX, ""); 40 | 41 | return Long.parseLong(userId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/typemappers/RelayConnectionTypeMapper.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers; 2 | 3 | /** 4 | * Created by bpatterson on 1/31/16. 5 | */ 6 | //@GraphQLTypeMapper(type = RelayConnection.class) 7 | public class RelayConnectionTypeMapper {//implements IGraphQLTypeMapper { 8 | /* 9 | @Override 10 | public boolean handlesType(Type type) { 11 | Class typeClass = (Class) (type instanceof ParameterizedType ? ((ParameterizedType) type).getRawType() : type); 12 | return RelayConnection.class.isAssignableFrom(typeClass); 13 | } 14 | 15 | @Override 16 | public GraphQLOutputType getOutputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 17 | ParameterizedType parameterizedType = (ParameterizedType) type; 18 | Class rawClass = (Class)parameterizedType.getRawType(); 19 | final Type pType = ((ParameterizedType) type).getActualTypeArguments()[0]; 20 | GraphQLObjectType.Builder rv = GraphQLObjectType.newObject().name(graphQLObjectMapper.getTypeNamingStrategy().getTypeName(rawClass)); 21 | GraphQLList edges = new GraphQLList(graphQLObjectMapper.getOutputType(new ParameterizedType() { 22 | @Override 23 | public Type[] getActualTypeArguments() { 24 | Type[] types = new Type[1]; 25 | types[0]= pType; 26 | return types; 27 | } 28 | 29 | @Override 30 | public Type getRawType() { 31 | return Edge.class; 32 | } 33 | 34 | @Override 35 | public Type getOwnerType() { 36 | return null; 37 | } 38 | } 39 | rv.field(GraphQLFieldDefinition.newFieldDefinition().name("edges") 40 | .type(edges).build()); 41 | } 42 | 43 | @Override 44 | public GraphQLInputType getInputType(IGraphQLObjectMapper graphQLObjectMapper, Type type) { 45 | return null; 46 | } 47 | */ 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/typemappers/relay/ConnectionCursorMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.typemappers.relay; 2 | 3 | import com.bretpatterson.schemagen.graphql.IGraphQLObjectMapper; 4 | import com.bretpatterson.schemagen.graphql.relay.ConnectionCursor; 5 | import graphql.Scalars; 6 | import graphql.schema.GraphQLNonNull; 7 | import graphql.schema.GraphQLOutputType; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | 13 | import java.lang.reflect.Type; 14 | 15 | import static org.junit.Assert.*; 16 | import static org.mockito.BDDMockito.*; 17 | 18 | @RunWith(MockitoJUnitRunner.class) 19 | public class ConnectionCursorMapperTest { 20 | 21 | @Mock 22 | private IGraphQLObjectMapper mockMapper; 23 | 24 | @Test 25 | public void test_handlesType_ConnectionCursor_true() throws Exception { 26 | // given 27 | Type type = ConnectionCursor.class; 28 | willReturn(ConnectionCursor.class).given(mockMapper).getClassFromType(type); 29 | ConnectionCursorMapper cut = createCut(); 30 | 31 | // when 32 | boolean result = cut.handlesType(mockMapper, type); 33 | 34 | // then 35 | assertTrue(result); 36 | } 37 | 38 | @Test 39 | public void test_handlesType_Other_false() throws Exception { 40 | // given 41 | Type type = Object.class; 42 | willReturn(Object.class).given(mockMapper).getClassFromType(type); 43 | ConnectionCursorMapper cut = createCut(); 44 | 45 | // when 46 | boolean result = cut.handlesType(mockMapper, type); 47 | 48 | // then 49 | assertFalse(result); 50 | } 51 | 52 | @Test 53 | public void test_getOutputType() throws Exception { 54 | // given 55 | ConnectionCursorMapper cut = createCut(); 56 | 57 | // when 58 | GraphQLOutputType result = cut.getOutputType(null, null); 59 | 60 | // then 61 | assertEquals(result, Scalars.GraphQLString); 62 | } 63 | 64 | @Test 65 | public void test_getInputType() throws Exception { 66 | // given 67 | ConnectionCursorMapper cut = createCut(); 68 | 69 | // when 70 | GraphQLOutputType result = cut.getOutputType(null, null); 71 | 72 | // then 73 | assertEquals(result, Scalars.GraphQLString); 74 | } 75 | 76 | private ConnectionCursorMapper createCut() { 77 | return new ConnectionCursorMapper(); 78 | } 79 | } -------------------------------------------------------------------------------- /src/test/java/com/bretpatterson/schemagen/graphql/utils/AnnotationUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.bretpatterson.schemagen.graphql.utils; 2 | 3 | import com.bretpatterson.schemagen.graphql.annotations.GraphQLTypeMapper; 4 | import org.junit.Test; 5 | 6 | import java.util.Map; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | public class AnnotationUtilsTest { 11 | 12 | @Test 13 | public void getClassesWithAnnotation() throws Exception { 14 | Map, GraphQLTypeMapper> classes = AnnotationUtils.getClassesWithAnnotation( 15 | GraphQLTypeMapper.class, "com.bretpatterson.schemagen"); 16 | 17 | assertFalse(classes.isEmpty()); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /wiki/gliffy/highPlurality.gliffy: -------------------------------------------------------------------------------- 1 | {"contentType":"application/gliffy+json","version":"1.1","metadata":{"title":"untitled","revision":0,"exportBorder":false},"embeddedResources":{"index":0,"resources":[]},"stage":{"objects":[{"x":324,"y":125,"rotation":0,"id":7,"uid":"com.gliffy.shape.uml.uml_v1.default.generalization","width":100,"height":100,"lockAspectRatio":false,"lockShape":false,"order":7,"graphic":{"type":"Line","Line":{"strokeWidth":1,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":4,"startArrowRotation":"auto","endArrowRotation":"auto","ortho":true,"interpolationType":"linear","cornerRadius":null,"controlPath":[[-24,2.5],[22.666666666666686,2.5],[69.33333333333331,2.5],[116,2.5]],"lockSegments":{}}},"children":[{"x":0,"y":0,"rotation":0,"id":8,"uid":null,"width":35,"height":28,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"both","vposition":"none","hposition":"none","html":"

0 .. *\n

guests

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":3,"px":1,"py":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":1,"px":0,"py":0.5}}},"linkMap":[]},{"x":200,"y":90,"rotation":0,"id":3,"uid":"com.gliffy.shape.basic.basic_v1.default.ellipse","width":100,"height":75,"lockAspectRatio":false,"lockShape":false,"order":3,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":5,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

CalendarItem

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]},{"x":440,"y":90,"rotation":0,"id":1,"uid":"com.gliffy.shape.basic.basic_v1.default.ellipse","width":100,"height":75,"lockAspectRatio":false,"lockShape":false,"order":1,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":2,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dropShadow":false,"state":0,"shadowX":0,"shadowY":0,"opacity":1}},"children":[{"x":2,"y":0,"rotation":0,"id":6,"uid":null,"width":96,"height":14,"lockAspectRatio":false,"lockShape":false,"order":"auto","graphic":{"type":"Text","Text":{"tid":null,"valign":"middle","overflow":"none","vposition":"none","hposition":"none","html":"

User

","paddingLeft":2,"paddingRight":2,"paddingBottom":2,"paddingTop":2}},"children":null}],"linkMap":[]}],"background":"#FFFFFF","width":540,"height":165,"maxWidth":5000,"maxHeight":5000,"nodeIndex":9,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":true,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#FFFFFF","stroke":"#333333","strokeWidth":2}},"lineStyles":{},"textStyles":{},"themeData":null}} -------------------------------------------------------------------------------- /wiki/images/highPlurality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpatters/schemagen-graphql/6f16fac5e01bfe97deeebb00bd2726bb5c3403f5/wiki/images/highPlurality.png -------------------------------------------------------------------------------- /wiki/images/sampleGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpatters/schemagen-graphql/6f16fac5e01bfe97deeebb00bd2726bb5c3403f5/wiki/images/sampleGraph.png --------------------------------------------------------------------------------