├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── com │ └── graphql │ └── example │ └── http │ ├── HttpMain.java │ ├── StarWarsWiring.java │ ├── data │ ├── Droid.java │ ├── Episode.java │ ├── FilmCharacter.java │ ├── Human.java │ └── StarWarsData.java │ ├── package-info.java │ └── utill │ ├── JsonKit.java │ └── QueryParameters.java └── resources ├── httpmain └── index.html └── starWarsSchemaAnnotated.graphqls /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | .idea 4 | *.iml 5 | .gradle -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 graphql-java 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-java-http-example 2 | 3 | An example of using graphql-java in a HTTP application. 4 | 5 | It demonstrates the use of `graphql IDL` to define a schema in a textual way. 6 | 7 | It also shows how to use `data loader` to ensure that most efficient way to load 8 | inside a graphql query 9 | 10 | Finally it shows how to combine graphql-java `Instrumentation` to create performance tracing 11 | and data loader effectiveness statistics. 12 | 13 | To build the code type 14 | 15 | ./gradlew build 16 | 17 | To run the code type 18 | 19 | ./gradlew run 20 | 21 | Point your browser at 22 | 23 | http://localhost:3000/ 24 | 25 | 26 | Some example graphql queries might be 27 | 28 | { 29 | hero { 30 | name 31 | friends { 32 | name 33 | friends { 34 | id 35 | name 36 | } 37 | 38 | } 39 | } 40 | } 41 | 42 | 43 | or maybe 44 | 45 | { 46 | luke: human(id: "1000") { 47 | ...HumanFragment 48 | } 49 | leia: human(id: "1003") { 50 | ...HumanFragment 51 | } 52 | } 53 | 54 | fragment HumanFragment on Human { 55 | name 56 | homePlanet 57 | friends { 58 | name 59 | __typename 60 | } 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.graphql-java' 2 | version '1.0-SNAPSHOT' 3 | 4 | 5 | apply plugin: 'java' 6 | apply plugin: 'groovy' 7 | apply plugin: 'application' 8 | 9 | sourceCompatibility = 1.8 10 | 11 | mainClassName = "com.graphql.example.http.HttpMain" 12 | 13 | repositories { 14 | mavenLocal() 15 | jcenter() 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | compile 'com.graphql-java:graphql-java:5.0' 21 | compile 'com.fasterxml.jackson.core:jackson-databind:2.8.8.1' 22 | compile 'com.google.code.gson:gson:2.8.0' 23 | compile 'org.eclipse.jetty:jetty-server:9.4.5.v20170502' 24 | compile 'com.squareup.okhttp3:okhttp:3.8.0' 25 | compile 'org.slf4j:slf4j-simple:1.7.22' 26 | 27 | testCompile group: 'junit', name: 'junit', version: '4.11' 28 | testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' 29 | testCompile 'org.codehaus.groovy:groovy-all:2.4.10' 30 | } 31 | 32 | task sourcesJar(type: Jar) { 33 | dependsOn classes 34 | classifier 'sources' 35 | from sourceSets.main.allSource 36 | } 37 | 38 | task javadocJar(type: Jar, dependsOn: javadoc) { 39 | classifier = 'javadoc' 40 | from javadoc.destinationDir 41 | } 42 | 43 | artifacts { 44 | archives sourcesJar 45 | archives javadocJar 46 | } 47 | 48 | task wrapper(type: Wrapper) { 49 | gradleVersion = '4.2.1' 50 | distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" 51 | } 52 | 53 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | org.gradle.daemon=true 3 | org.gradle.parallel=true 4 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 5 | 6 | bintray.user=DUMMY_USER 7 | bintray.key=DUMMY_KEY -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-java/graphql-java-http-example/98a4a899adf4a97107aea058c079e0c1222e27cf/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 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 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'graphql-java-http-example' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/HttpMain.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http; 2 | 3 | import com.graphql.example.http.utill.JsonKit; 4 | import com.graphql.example.http.utill.QueryParameters; 5 | import graphql.ExecutionInput; 6 | import graphql.ExecutionResult; 7 | import graphql.GraphQL; 8 | import graphql.execution.instrumentation.ChainedInstrumentation; 9 | import graphql.execution.instrumentation.Instrumentation; 10 | import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation; 11 | import graphql.execution.instrumentation.tracing.TracingInstrumentation; 12 | import graphql.schema.GraphQLSchema; 13 | import graphql.schema.idl.RuntimeWiring; 14 | import graphql.schema.idl.SchemaGenerator; 15 | import graphql.schema.idl.SchemaParser; 16 | import graphql.schema.idl.TypeDefinitionRegistry; 17 | import org.dataloader.DataLoaderRegistry; 18 | import org.eclipse.jetty.server.Handler; 19 | import org.eclipse.jetty.server.Request; 20 | import org.eclipse.jetty.server.Server; 21 | import org.eclipse.jetty.server.handler.AbstractHandler; 22 | import org.eclipse.jetty.server.handler.HandlerList; 23 | import org.eclipse.jetty.server.handler.ResourceHandler; 24 | 25 | import javax.servlet.ServletException; 26 | import javax.servlet.http.HttpServletRequest; 27 | import javax.servlet.http.HttpServletResponse; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.InputStreamReader; 31 | import java.io.Reader; 32 | 33 | import static graphql.ExecutionInput.newExecutionInput; 34 | import static graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions.newOptions; 35 | import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring; 36 | import static java.util.Arrays.asList; 37 | 38 | /** 39 | * An very simple example of serving a qraphql schema over http. 40 | *

41 | * More info can be found here : http://graphql.org/learn/serving-over-http/ 42 | */ 43 | @SuppressWarnings("unchecked") 44 | public class HttpMain extends AbstractHandler { 45 | 46 | static final int PORT = 3000; 47 | static GraphQLSchema starWarsSchema = null; 48 | 49 | public static void main(String[] args) throws Exception { 50 | // 51 | // This example uses Jetty as an embedded HTTP server 52 | Server server = new Server(PORT); 53 | // 54 | // In Jetty, handlers are how your get called backed on a request 55 | HttpMain main_handler = new HttpMain(); 56 | 57 | // this allows us to server our index.html and GraphIQL JS code 58 | ResourceHandler resource_handler = new ResourceHandler(); 59 | resource_handler.setDirectoriesListed(false); 60 | resource_handler.setWelcomeFiles(new String[]{"index.html"}); 61 | resource_handler.setResourceBase("./src/main/resources/httpmain"); 62 | 63 | HandlerList handlers = new HandlerList(); 64 | handlers.setHandlers(new Handler[]{resource_handler, main_handler}); 65 | server.setHandler(handlers); 66 | 67 | server.start(); 68 | 69 | server.join(); 70 | } 71 | 72 | @Override 73 | public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 74 | if ("/graphql".equals(target)) { 75 | baseRequest.setHandled(true); 76 | handleStarWars(request, response); 77 | } 78 | } 79 | 80 | private void handleStarWars(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException { 81 | // 82 | // this builds out the parameters we need like the graphql query from the http request 83 | QueryParameters parameters = QueryParameters.from(httpRequest); 84 | if (parameters.getQuery() == null) { 85 | // 86 | // how to handle nonsensical requests is up to your application 87 | httpResponse.setStatus(400); 88 | return; 89 | } 90 | 91 | ExecutionInput.Builder executionInput = newExecutionInput() 92 | .query(parameters.getQuery()) 93 | .operationName(parameters.getOperationName()) 94 | .variables(parameters.getVariables()); 95 | 96 | // 97 | // the context object is something that means something to down stream code. It is instructions 98 | // from yourself to your other code such as DataFetchers. The engine passes this on unchanged and 99 | // makes it available to inner code 100 | // 101 | // the graphql guidance says : 102 | // 103 | // - GraphQL should be placed after all authentication middleware, so that you 104 | // - have access to the same session and user information you would in your 105 | // - HTTP endpoint handlers. 106 | // 107 | StarWarsWiring.Context context = new StarWarsWiring.Context(); 108 | executionInput.context(context); 109 | 110 | // 111 | // you need a schema in order to execute queries 112 | GraphQLSchema schema = buildStarWarsSchema(); 113 | 114 | // 115 | // This example uses the DataLoader technique to ensure that the most efficient 116 | // loading of data (in this case StarWars characters) happens. We pass that to data 117 | // fetchers via the graphql context object. 118 | // 119 | DataLoaderRegistry dataLoaderRegistry = context.getDataLoaderRegistry(); 120 | 121 | 122 | DataLoaderDispatcherInstrumentation dlInstrumentation = 123 | new DataLoaderDispatcherInstrumentation(dataLoaderRegistry, newOptions().includeStatistics(true)); 124 | 125 | Instrumentation instrumentation = new ChainedInstrumentation( 126 | asList(new TracingInstrumentation(), dlInstrumentation) 127 | ); 128 | 129 | // finally you build a runtime graphql object and execute the query 130 | GraphQL graphQL = GraphQL 131 | .newGraphQL(schema) 132 | // instrumentation is pluggable 133 | .instrumentation(instrumentation) 134 | .build(); 135 | ExecutionResult executionResult = graphQL.execute(executionInput.build()); 136 | 137 | returnAsJson(httpResponse, executionResult); 138 | } 139 | 140 | 141 | private void returnAsJson(HttpServletResponse response, ExecutionResult executionResult) throws IOException { 142 | response.setContentType("application/json"); 143 | response.setStatus(HttpServletResponse.SC_OK); 144 | JsonKit.toJson(response, executionResult.toSpecification()); 145 | } 146 | 147 | private GraphQLSchema buildStarWarsSchema() { 148 | // 149 | // using lazy loading here ensure we can debug the schema generation 150 | // and potentially get "wired" components that cant be accessed 151 | // statically. 152 | // 153 | // A full application would use a dependency injection framework (like Spring) 154 | // to manage that lifecycle. 155 | // 156 | if (starWarsSchema == null) { 157 | 158 | // 159 | // reads a file that provides the schema types 160 | // 161 | Reader streamReader = loadSchemaFile("starWarsSchemaAnnotated.graphqls"); 162 | TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(streamReader); 163 | 164 | RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring() 165 | .type(newTypeWiring("Query") 166 | .dataFetcher("hero", StarWarsWiring.heroDataFetcher) 167 | .dataFetcher("human", StarWarsWiring.humanDataFetcher) 168 | .dataFetcher("droid", StarWarsWiring.droidDataFetcher) 169 | ) 170 | .type(newTypeWiring("Human") 171 | .dataFetcher("friends", StarWarsWiring.friendsDataFetcher) 172 | ) 173 | .type(newTypeWiring("Droid") 174 | .dataFetcher("friends", StarWarsWiring.friendsDataFetcher) 175 | ) 176 | 177 | .type(newTypeWiring("Character") 178 | .typeResolver(StarWarsWiring.characterTypeResolver) 179 | ) 180 | .type(newTypeWiring("Episode") 181 | .enumValues(StarWarsWiring.episodeResolver) 182 | ) 183 | .build(); 184 | 185 | // finally combine the logical schema with the physical runtime 186 | starWarsSchema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring); 187 | } 188 | return starWarsSchema; 189 | } 190 | 191 | @SuppressWarnings("SameParameterValue") 192 | private Reader loadSchemaFile(String name) { 193 | InputStream stream = getClass().getClassLoader().getResourceAsStream(name); 194 | return new InputStreamReader(stream); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/StarWarsWiring.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http; 2 | 3 | import com.graphql.example.http.data.Episode; 4 | import com.graphql.example.http.data.FilmCharacter; 5 | import com.graphql.example.http.data.Human; 6 | import com.graphql.example.http.data.StarWarsData; 7 | import graphql.schema.DataFetcher; 8 | import graphql.schema.GraphQLObjectType; 9 | import graphql.schema.TypeResolver; 10 | import graphql.schema.idl.EnumValuesProvider; 11 | import org.dataloader.BatchLoader; 12 | import org.dataloader.DataLoader; 13 | import org.dataloader.DataLoaderRegistry; 14 | 15 | import java.util.List; 16 | import java.util.concurrent.CompletableFuture; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * This is our wiring used to put behaviour to a graphql type. 21 | */ 22 | public class StarWarsWiring { 23 | 24 | /** 25 | * The context object is passed to each level of a graphql query and in this case it contains 26 | * the data loader registry. This allows us to keep our data loaders per request since 27 | * they cache data and cross request caches are often not what you want. 28 | */ 29 | public static class Context { 30 | 31 | final DataLoaderRegistry dataLoaderRegistry; 32 | 33 | public Context() { 34 | this.dataLoaderRegistry = new DataLoaderRegistry(); 35 | dataLoaderRegistry.register("characters", newCharacterDataLoader()); 36 | } 37 | 38 | public DataLoaderRegistry getDataLoaderRegistry() { 39 | return dataLoaderRegistry; 40 | } 41 | 42 | public DataLoader getCharacterDataLoader() { 43 | return dataLoaderRegistry.getDataLoader("characters"); 44 | } 45 | } 46 | 47 | private static List getCharacterDataViaBatchHTTPApi(List keys) { 48 | return keys.stream().map(StarWarsData::getCharacterData).collect(Collectors.toList()); 49 | } 50 | 51 | // a batch loader function that will be called with N or more keys for batch loading 52 | private static BatchLoader characterBatchLoader = keys -> { 53 | 54 | // 55 | // we are using multi threading here. Imagine if getCharacterDataViaBatchHTTPApi was 56 | // actually a HTTP call - its not here - but it could be done asynchronously as 57 | // a batch API call say 58 | // 59 | // 60 | // direct return of values 61 | //CompletableFuture.completedFuture(getCharacterDataViaBatchHTTPApi(keys)) 62 | // 63 | // or 64 | // 65 | // async supply of values 66 | return CompletableFuture.supplyAsync(() -> getCharacterDataViaBatchHTTPApi(keys)); 67 | }; 68 | 69 | // a data loader for characters that points to the character batch loader 70 | private static DataLoader newCharacterDataLoader() { 71 | return new DataLoader<>(characterBatchLoader); 72 | } 73 | 74 | // we define the normal StarWars data fetchers so we can point them at our data loader 75 | static DataFetcher humanDataFetcher = environment -> { 76 | String id = environment.getArgument("id"); 77 | Context ctx = environment.getContext(); 78 | return ctx.getCharacterDataLoader().load(id); 79 | }; 80 | 81 | 82 | static DataFetcher droidDataFetcher = environment -> { 83 | String id = environment.getArgument("id"); 84 | Context ctx = environment.getContext(); 85 | return ctx.getCharacterDataLoader().load(id); 86 | }; 87 | 88 | static DataFetcher heroDataFetcher = environment -> { 89 | Context ctx = environment.getContext(); 90 | return ctx.getCharacterDataLoader().load("2001"); // R2D2 91 | }; 92 | 93 | static DataFetcher friendsDataFetcher = environment -> { 94 | FilmCharacter character = environment.getSource(); 95 | List friendIds = character.getFriends(); 96 | Context ctx = environment.getContext(); 97 | return ctx.getCharacterDataLoader().loadMany(friendIds); 98 | }; 99 | 100 | /** 101 | * Character in the graphql type system is an Interface and something needs 102 | * to decide that concrete graphql object type to return 103 | */ 104 | static TypeResolver characterTypeResolver = environment -> { 105 | FilmCharacter character = (FilmCharacter) environment.getObject(); 106 | if (character instanceof Human) { 107 | return (GraphQLObjectType) environment.getSchema().getType("Human"); 108 | } else { 109 | return (GraphQLObjectType) environment.getSchema().getType("Droid"); 110 | } 111 | }; 112 | 113 | static EnumValuesProvider episodeResolver = Episode::valueOf; 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/data/Droid.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http.data; 2 | 3 | import java.util.List; 4 | 5 | public class Droid implements FilmCharacter { 6 | final String id; 7 | final String name; 8 | final List friends; 9 | final List appearsIn; 10 | final String primaryFunction; 11 | 12 | public Droid(String id, String name, List friends, List appearsIn, String primaryFunction) { 13 | this.id = id; 14 | this.name = name; 15 | this.friends = friends; 16 | this.appearsIn = appearsIn; 17 | this.primaryFunction = primaryFunction; 18 | } 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public List getFriends() { 29 | return friends; 30 | } 31 | 32 | public List getAppearsIn() { 33 | return appearsIn; 34 | } 35 | 36 | public String getPrimaryFunction() { 37 | return primaryFunction; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "Droid{" + 43 | "id='" + id + '\'' + 44 | ", name='" + name + '\'' + 45 | '}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/data/Episode.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http.data; 2 | 3 | public enum Episode { 4 | //# Released in 1977 5 | NEWHOPE, 6 | //# Released in 1980. 7 | EMPIRE, 8 | //# Released in 1983. 9 | JEDI 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/data/FilmCharacter.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http.data; 2 | 3 | import java.util.List; 4 | 5 | public interface FilmCharacter { 6 | String getId(); 7 | 8 | String getName(); 9 | 10 | List getFriends(); 11 | 12 | List getAppearsIn(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/data/Human.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http.data; 2 | 3 | import java.util.List; 4 | 5 | public class Human implements FilmCharacter { 6 | final String id; 7 | final String name; 8 | final List friends; 9 | final List appearsIn; 10 | final String homePlanet; 11 | 12 | public Human(String id, String name, List friends, List appearsIn, String homePlanet) { 13 | this.id = id; 14 | this.name = name; 15 | this.friends = friends; 16 | this.appearsIn = appearsIn; 17 | this.homePlanet = homePlanet; 18 | } 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public List getFriends() { 29 | return friends; 30 | } 31 | 32 | public List getAppearsIn() { 33 | return appearsIn; 34 | } 35 | 36 | public String getHomePlanet() { 37 | return homePlanet; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "Human{" + 43 | "id='" + id + '\'' + 44 | ", name='" + name + '\'' + 45 | '}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/data/StarWarsData.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http.data; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | import static java.util.Arrays.asList; 7 | 8 | /** 9 | * This contains our data used in this example. Imagine it is a database or an upstream REST resource 10 | * pf data (and not just an in memory representation) 11 | */ 12 | @SuppressWarnings("unused") 13 | public class StarWarsData { 14 | 15 | 16 | static Human luke = new Human( 17 | "1000", 18 | "Luke Skywalker", 19 | asList("1002", "1003", "2000", "2001"), 20 | asList(4, 5, 6), 21 | "Tatooine" 22 | ); 23 | 24 | static Human vader = new Human( 25 | "1001", 26 | "Darth Vader", 27 | asList("1004"), 28 | asList(4, 5, 6), 29 | "Tatooine" 30 | ); 31 | 32 | static Human han = new Human( 33 | "1002", 34 | "Han Solo", 35 | asList("1000", "1003", "2001"), 36 | asList(4, 5, 6), 37 | null 38 | ); 39 | 40 | static Human leia = new Human( 41 | "1003", 42 | "Leia Organa", 43 | asList("1000", "1002", "2000", "2001"), 44 | asList(4, 5, 6), 45 | "Alderaan" 46 | ); 47 | 48 | static Human tarkin = new Human( 49 | "1004", 50 | "Wilhuff Tarkin", 51 | asList("1001"), 52 | asList(4), 53 | null 54 | ); 55 | 56 | static Map humanData = new LinkedHashMap<>(); 57 | 58 | static { 59 | humanData.put("1000", luke); 60 | humanData.put("1001", vader); 61 | humanData.put("1002", han); 62 | humanData.put("1003", leia); 63 | humanData.put("1004", tarkin); 64 | } 65 | 66 | static Droid threepio = new Droid( 67 | "2000", 68 | "C-3PO", 69 | asList("1000", "1002", "1003", "2001"), 70 | asList(4, 5, 6), 71 | "Protocol" 72 | ); 73 | 74 | static Droid artoo = new Droid( 75 | "2001", 76 | "R2-D2", 77 | asList("1000", "1002", "1003"), 78 | asList(4, 5, 6), 79 | "Astromech" 80 | ); 81 | 82 | static Map droidData = new LinkedHashMap<>(); 83 | 84 | static { 85 | droidData.put("2000", threepio); 86 | droidData.put("2001", artoo); 87 | 88 | } 89 | 90 | public static boolean isHuman(String id) { 91 | return humanData.get(id) != null; 92 | } 93 | 94 | public static Object getCharacterData(String id) { 95 | if (humanData.get(id) != null) { 96 | return humanData.get(id); 97 | } else if (droidData.get(id) != null) { 98 | return droidData.get(id); 99 | } 100 | return null; 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this code is to show an example of serving a graphql query over HTTP 3 | * 4 | * More info can be found here : http://graphql.org/learn/serving-over-http/ 5 | * 6 | * There are more concerns in a fully fledged application such as your approach to permissions 7 | * and authentication and so on that are not shown here. 8 | * 9 | * The backing data is the "star wars" example schema. A fairly complex example query is as follows : 10 | * 11 | *
12 |  * {@code
13 |  * {
14 |  *      luke: human(id: "1000") {
15 |  *          ...HumanFragment
16 |  *      }
17 |  *      leia: human(id: "1003") {
18 |  *          ...HumanFragment
19 |  *      }
20 |  *  }
21 |  *
22 |  *  fragment HumanFragment on Human {
23 |  *      name
24 |  *      homePlanet
25 |  *      friends {
26 |  *      name
27 |  *      __typename
28 |  *  }
29 |  * }
30 |  *
31 |  * }
32 |  * 
{ 33 | */ 34 | package com.graphql.example.http; -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/utill/JsonKit.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http.utill; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.reflect.TypeToken; 6 | 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | import java.util.Collections; 10 | import java.util.Map; 11 | 12 | /** 13 | * This example code chose to use GSON as its JSON parser. Any JSON parser should be fine 14 | */ 15 | public class JsonKit { 16 | static final Gson GSON = new GsonBuilder() 17 | // 18 | // This is important because the graphql spec says that null values should be present 19 | // 20 | .serializeNulls() 21 | .create(); 22 | 23 | public static void toJson(HttpServletResponse response, Object result) throws IOException { 24 | GSON.toJson(result, response.getWriter()); 25 | } 26 | 27 | public static Map toMap(String jsonStr) { 28 | if (jsonStr == null || jsonStr.trim().length() == 0) { 29 | return Collections.emptyMap(); 30 | } 31 | // gson uses type tokens for generic input like Map 32 | TypeToken> typeToken = new TypeToken>() { 33 | }; 34 | Map map = GSON.fromJson(jsonStr, typeToken.getType()); 35 | return map == null ? Collections.emptyMap() : map; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/graphql/example/http/utill/QueryParameters.java: -------------------------------------------------------------------------------- 1 | package com.graphql.example.http.utill; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import java.io.BufferedReader; 5 | import java.io.IOException; 6 | import java.util.Collections; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Graphql clients can send GET or POST HTTP requests. The spec does not make an explicit 12 | * distinction. So you may need to handle both. The following was tested using 13 | * a graphiql client tool found here : https://github.com/skevy/graphiql-app 14 | * 15 | * You should consider bundling graphiql in your application 16 | * 17 | * https://github.com/graphql/graphiql 18 | * 19 | * This outlines more information on how to handle parameters over http 20 | * 21 | * http://graphql.org/learn/serving-over-http/ 22 | */ 23 | public class QueryParameters { 24 | 25 | String query; 26 | String operationName; 27 | Map variables = Collections.emptyMap(); 28 | 29 | public String getQuery() { 30 | return query; 31 | } 32 | 33 | public String getOperationName() { 34 | return operationName; 35 | } 36 | 37 | public Map getVariables() { 38 | return variables; 39 | } 40 | 41 | public static QueryParameters from(HttpServletRequest request) { 42 | QueryParameters parameters = new QueryParameters(); 43 | if ("POST".equalsIgnoreCase(request.getMethod())) { 44 | Map json = readJSON(request); 45 | parameters.query = (String) json.get("query"); 46 | parameters.operationName = (String) json.get("operationName"); 47 | parameters.variables = getVariables(json.get("variables")); 48 | } else { 49 | parameters.query = request.getParameter("query"); 50 | parameters.operationName = request.getParameter("operationName"); 51 | parameters.variables = getVariables(request.getParameter("variables")); 52 | } 53 | return parameters; 54 | } 55 | 56 | 57 | private static Map getVariables(Object variables) { 58 | if (variables instanceof Map) { 59 | Map inputVars = (Map) variables; 60 | Map vars = new HashMap<>(); 61 | inputVars.forEach((k, v) -> vars.put(String.valueOf(k), v)); 62 | return vars; 63 | } 64 | return JsonKit.toMap(String.valueOf(variables)); 65 | } 66 | 67 | private static Map readJSON(HttpServletRequest request) { 68 | String s = readPostBody(request); 69 | return JsonKit.toMap(s); 70 | } 71 | 72 | private static String readPostBody(HttpServletRequest request) { 73 | try { 74 | StringBuilder sb = new StringBuilder(); 75 | BufferedReader reader = request.getReader(); 76 | int c; 77 | while ((c = reader.read()) != -1) { 78 | sb.append((char) c); 79 | } 80 | return sb.toString(); 81 | } catch (IOException e) { 82 | throw new RuntimeException(e); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/resources/httpmain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Loading...
35 | 134 | 135 | -------------------------------------------------------------------------------- /src/main/resources/starWarsSchemaAnnotated.graphqls: -------------------------------------------------------------------------------- 1 | # 2 | # Schemas must have at least a query root type 3 | # 4 | schema { 5 | query: Query 6 | } 7 | 8 | # This is the type that will be the root of our query, and the 9 | # entry point into our schema. It gives us the ability to fetch 10 | # objects by their IDs, as well as to fetch the undisputed hero 11 | # of the *Star Wars* trilogy, `R2-D2`, directly. 12 | type Query { 13 | # If omitted, returns the hero of the whole saga. If 14 | # provided, returns the hero of that particular episode. 15 | hero( 16 | # You can indicate what episode the hero was in 17 | episode: Episode 18 | ) : Character 19 | 20 | human( 21 | # The id of the human you are interested in 22 | id : String 23 | ) : Human 24 | 25 | droid( 26 | # The non null id of the droid you are interested in 27 | id: ID! 28 | ): Droid 29 | } 30 | 31 | # One of the films in the Star Wars Trilogy 32 | enum Episode { 33 | # Released in 1977 34 | NEWHOPE 35 | # Released in 1980. 36 | EMPIRE 37 | # Released in 1983. 38 | JEDI 39 | } 40 | 41 | # A character in the Star Wars Trilogy 42 | interface Character { 43 | # The id of the character. 44 | id: ID! 45 | # The name of the character. 46 | name: String! 47 | # The friends of the character, or an empty list if they 48 | # have none. 49 | friends: [Character] 50 | # Which movies they appear in. 51 | appearsIn: [Episode]! 52 | # All secrets about their past. 53 | secretBackstory : String @deprecated(reason : "We have decided that this is not canon") 54 | } 55 | 56 | # A humanoid creature in the Star Wars universe. 57 | type Human implements Character { 58 | # The id of the human. 59 | id: ID! 60 | # The name of the human. 61 | name: String! 62 | # The friends of the human, or an empty list if they have none. 63 | friends: [Character] 64 | # Which movies they appear in. 65 | appearsIn: [Episode]! 66 | # The home planet of the human, or null if unknown. 67 | homePlanet: String 68 | # Where are they from and how they came to be who they are. 69 | secretBackstory : String @deprecated(reason : "We have decided that this is not canon") 70 | } 71 | 72 | # A mechanical creature in the Star Wars universe. 73 | type Droid implements Character { 74 | # The id of the droid. 75 | id: ID! 76 | # The name of the droid. 77 | name: String! 78 | # The friends of the droid, or an empty list if they have none. 79 | friends: [Character] 80 | # Which movies they appear in. 81 | appearsIn: [Episode]! 82 | # The primary function of the droid. 83 | primaryFunction: String 84 | # Construction date and the name of the designer. 85 | secretBackstory : String @deprecated(reason : "We have decided that this is not canon") 86 | } 87 | 88 | --------------------------------------------------------------------------------