diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..f121de793 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +/.gradle/ +/build/ +/bin/ + +#Eclipse +.project +.classpath +.settings + +# Maven +/target/ +/pom.xml +local.properties \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..11938c882 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Microsoft Graph + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..261f884bc --- /dev/null +++ b/build.gradle @@ -0,0 +1,344 @@ +/* + * This build file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java Library project to get you started. + * For more details take a look at the Java Libraries chapter in the Gradle + * user guide available at https://docs.gradle.org/4.5/userguide/java_library_plugin.html + */ + +// Apply the java-library plugin to add support for Java Library +apply plugin: 'java-library' +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'maven' +apply plugin: 'maven-publish' +apply plugin: 'signing' +apply plugin: 'jacoco' + +// In this section you declare where to find the dependencies of your project +repositories { + // Use jcenter for resolving your dependencies. + // You can declare any Maven/Ivy/file repository here. + jcenter() + mavenCentral() +} + +dependencies { + // This dependency is exported to consumers, that is to say found on their compile classpath. + api 'org.apache.commons:commons-math3:3.6.1' + + // This dependency is used internally, and not exposed to consumers on their own compile classpath. + implementation 'com.google.guava:guava:20.0' + + // Use JUnit test framework + testImplementation 'junit:junit:4.12' + + api 'com.squareup.okhttp3:okhttp:3.12.1' + + // https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple + compile group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1' +} + +def pomConfig = { + licenses { + license([:]) { + name "MIT License" + url "http://opensource.org/licenses/MIT" + distribution "repo" + } + } +} + +//Publishing tasks- +//Maven Central Snapshot: publishSnapshotPublicationToMavenRepository +//Maven Central Release: publishMavenCentralReleasePublicationToMaven2Repository +//Bintray Snapshot: publishSnapshotPublicationToMaven3Repository +//Bintray Release: uploadArchives + +publishing { + + publications { + + maven(MavenPublication) { + + groupId 'com.microsoft.graph' + + artifactId 'microsoft-graph-core' + + version "${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}${mavenArtifactSuffix}" + + from components.java + + artifact sourceJar + pom.withXml { + def root = asNode() + root.appendNode('name', 'Microsoft Graph Core SDK for Java') + root.appendNode('url', 'https://github.com/microsoftgraph/msgraph-sdk-java-core') + root.children().last() + pomConfig + def pomFile = file("${project.buildDir}/libs/microsoft-graph-core.pom") + writeTo(pomFile) + } + + } + Snapshot(MavenPublication) { + customizePom(pom) + groupId 'com.microsoft.graph' + artifactId 'microsoft-graph-core' + version "${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}${mavenCentralSnapshotArtifactSuffix}" + from components.java + pom.withXml { + def pomFile = file("${project.buildDir}/generated-pom.xml") + writeTo(pomFile) + } + artifact(sourceJar) { + classifier = 'sources' + } + artifact(javadocJar) { + classifier = 'javadoc' + } + } + + mavenCentralRelease(MavenPublication) { + customizePom(pom) + groupId 'com.microsoft.graph' + artifactId 'microsoft-graph-core' + version "${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}" + from components.java + pom.withXml { + def pomFile = file("${project.buildDir}/generated-pom.xml") + writeTo(pomFile) + def pomAscFile = signing.sign(pomFile).signatureFiles[0] + artifact(pomAscFile) { + classifier = null + extension = 'pom.asc' + } + } + artifact(sourceJar) { + classifier = 'sources' + } + artifact(javadocJar) { + classifier = 'javadoc' + } + project.tasks.signArchives.signatureFiles.each { + artifact(it) { + def matcher = it.file =~ /-(sources|javadoc)\.jar\.asc$/ + if(matcher.find()){ + classifier = matcher.group(1) + } + else{ + classifier = null + } + extension = 'jar.asc' + } + } + } + } + repositories { + maven { + url = project.property('mavenCentralSnapshotUrl') + + credentials { + if (project.rootProject.file('local.properties').exists()) { + + Properties properties = new Properties() + + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + + username = properties.getProperty('sonatypeUsername') + + password = properties.getProperty('sonatypePassword') + + } + } + } + + maven { + url = project.property('mavenCentralReleaseUrl') + + credentials { + if (project.rootProject.file('local.properties').exists()) { + + Properties properties = new Properties() + + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + + username = properties.getProperty('sonatypeUsername') + + password = properties.getProperty('sonatypePassword') + + } + } + } + + maven { + url = project.property('mavenBintraySnapshotUrl') + + credentials { + if (project.rootProject.file('local.properties').exists()) { + + Properties properties = new Properties() + + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + + username = (properties.containsKey('bintray.user')) ? properties.getProperty('bintray.user').toLowerCase() : "BINTRAY_USERNAME" + + password = properties.getProperty('bintray.apikey') + + } + } + } + } + +} + +task sourceJar(type: Jar) { + classifier = 'sources' + from sourceSets.main.allJava +} + +compileJava { + sourceCompatibility = 1.7 + targetCompatibility = 1.7 +} + +def getVersionCode() { + return mavenMajorVersion.toInteger() * 10000 + mavenMinorVersion.toInteger() * 100 + mavenPatchVersion.toInteger() +} + +def getVersionName() { + return "${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}${mavenArtifactSuffix}" +} + +uploadArchives { + + def bintrayUsername = "" + + def bintrayApikey = "" + + if (project.rootProject.file('local.properties').exists()) { + + Properties properties = new Properties() + + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + + bintrayUsername = properties.getProperty('bintray.user') + + bintrayApikey = properties.getProperty('bintray.apikey') + + } + + configuration = configurations.archives + + repositories.mavenDeployer { + + pom { + + setGroupId project.mavenGroupId + + setArtifactId project.mavenArtifactId + + setVersion getVersionName() + + } + + repository (url: project.mavenRepoUrl) { + + url = url + "/" + getVersionName() + + authentication( + + // put these values in local file ~/.gradle/gradle.properties + + userName: project.hasProperty("bintrayUsername") ? project.bintrayUsername : bintrayUsername, + + password: project.hasProperty("bintrayApikey") ? project.bintrayApikey : bintrayApikey + + ) + + } + + } + +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives jar + archives sourceJar + archives javadocJar +} + +signing { + sign configurations.archives +} +tasks.withType(Sign)*.enabled = mavenCentralPublishingEnabled.toBoolean() + +def customizePom(pom) { + pom.withXml { + def root = asNode() + + root.dependencies.removeAll { dep -> + dep.scope == "test" + } + + root.children().last() + { + resolveStrategy = Closure.DELEGATE_FIRST + + description 'Microsoft Graph Core SDK' + name 'Microsoft Graph Java Core SDK' + url 'https://github.com/microsoftgraph/msgraph-sdk-java-core' + organization { + name 'Microsoft' + url 'https://github.com/microsoftgraph/msgraph-sdk-java-core' + } + issueManagement { + system 'GitHub' + url 'https://github.com/microsoftgraph/msgraph-sdk-java-core/issues' + } + licenses { + license { + name "MIT License" + url "http://opensource.org/licenses/MIT" + distribution "repo" + } + } + scm { + url 'https://github.com/microsoftgraph/msgraph-sdk-java-core' + connection 'scm:git:git://github.com/microsoftgraph/msgraph-sdk-java-core.git' + developerConnection 'scm:git:ssh://git@github.com:microsoftgraph/msgraph-sdk-java-core.git' + } + developers { + developer { + name 'Microsoft' + } + } + } + } +} + +gradle.taskGraph.whenReady { taskGraph -> + if (project.rootProject.file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + tasks.withType(Sign)*.enabled = (properties.containsKey('enableSigning')) ? properties.getProperty('enableSigning').toBoolean() : false + allprojects { ext."signing.keyId" = properties.getProperty('signing.keyId') } + allprojects { ext."signing.secretKeyRingFile" = properties.getProperty('signing.secretKeyRingFile') } + allprojects { ext."signing.password" = properties.getProperty('signing.password') } + } +} + +model { + tasks.generatePomFileForMavenCentralReleasePublication { + destination = file("$buildDir/generated-pom.xml") + } + tasks.publishMavenCentralReleasePublicationToMavenLocal { + dependsOn project.tasks.signArchives + } + tasks.publishMavenCentralReleasePublicationToMaven2Repository { + dependsOn project.tasks.signArchives + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..eb7f41cfc --- /dev/null +++ b/gradle.properties @@ -0,0 +1,46 @@ +# Project-wide Gradle settings. + +# IDE users: +# Settings specified in this file will override any Gradle settings +# configured through the IDE. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# The size of the library demands a large amount of RAM to build. Increase as necessary if you get GC errors +## linux requires 10G, OSX requires 11G +org.gradle.jvmargs=-XX:MaxPermSize=512m -Xmx2g + +mavenRepoUrl = https://api.bintray.com/content/microsoftgraph/Maven/microsoft-graph +mavenBintraySnapshotUrl = http://oss.jfrog.org/artifactory/oss-snapshot-local +mavenGroupId = com.microsoft.graph +mavenArtifactId = microsoft-graph-core +mavenMajorVersion = 0 +mavenMinorVersion = 1 +mavenPatchVersion = 0 +mavenArtifactSuffix = +nightliesUrl = http://dl.bintray.com/MicrosoftGraph/Maven + +#These values are used to run functional tests +#If you wish to run the functional tests, edit the gradle.properties +#file in your user directory instead of adding them here. +#ex: C:\Users\username\.gradle\gradle.properties +ClientId="CLIENT_ID" +Username="USERNAME" +Password="PASSWORD" + +#enable mavenCentralPublishingEnabled to publish to maven central +mavenCentralSnapshotUrl=https://oss.sonatype.org/content/repositories/snapshots +mavenCentralReleaseUrl=https://oss.sonatype.org/service/local/staging/deploy/maven2 +mavenCentralSnapshotArtifactSuffix = -SNAPSHOT +mavenCentralPublishingEnabled=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..92165eede --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip diff --git a/gradlew b/gradlew new file mode 100644 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..f9553162f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/readme.md b/readme.md index cea3bfaf2..65c6528c4 100644 --- a/readme.md +++ b/readme.md @@ -1 +1,150 @@ -# Microsoft Graph Core SDK for Java \ No newline at end of file +# Microsoft Graph Core SDK for Java + +Get started with the Microsoft Graph Core SDK for Java by integrating the [Microsoft Graph API](https://graph.microsoft.io/en-us/getting-started) into your Java application! + +## 1. Installation + +### 1.1 Install via Gradle + +Add the repository and a compile dependency for `microsoft-graph-core` to your project's `build.gradle`: + +```gradle +repository { + jcenter() + jcenter{ + url 'http://oss.jfrog.org/artifactory/oss-snapshot-local' + } +} + +dependency { + // Include the sdk as a dependency + compile('com.microsoft.graph:microsoft-graph-core:0.1.0-SNAPSHOT') +} +``` + +### 1.2 Install via Maven +Add the dependency in `dependencies` in pom.xml +```dependency + + com.microsoft.graph + microsoft-graph-core + 0.1.0-SNAPSHOT + +``` + +Add `profiles` in `project` to download Snapshot release binary: +``` + + + allow-snapshots + + true + + + + snapshots-repo + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + +``` + +### 1.3 Enable ProGuard (Android) +The nature of the Graph API is such that the SDK needs quite a large set of classes to describe its functionality. You need to ensure that [ProGuard](https://developer.android.com/studio/build/shrink-code.html) is enabled on your project. Otherwise, you will incur long build times for functionality that is not necessarily relevant to your particular application. If you are still hitting the 64K method limit, you can also enable [multidexing](https://developer.android.com/studio/build/multidex.html). + +## 2. Getting started + +### 2.1 Register your application + +Register your application by following the steps at [Register your app with the Azure AD v2.0 endpoint](https://developer.microsoft.com/en-us/graph/docs/concepts/auth_register_app_v2). + +### 2.2 Create an IAuthenticationProvider object + +An instance of the **HttpClients** class handles building client. To create a new instance of this class, you need to provide an instance of `ICoreAuthenticationProvider`, which can authenticate requests to Microsoft Graph. + +### 2.3 Get a HttpClients object +You must get a **HttpClients** object to make requests against the service. + +```java +OkHttpClient client = HttpClients.createDefault(iCoreAuthenticationProvider); +``` + +## 3. Make requests against the service + +After you have a HttpClients that is authenticated, you can begin making calls against the service. The requests against the service look like our [REST API](https://developer.microsoft.com/en-us/graph/docs/concepts/overview). + +### 3.1 Get the user's details + +To retrieve the user's details + +```java +Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me/").build(); + +client.newCall(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) throws IOException { + String responseBody = response.body().string(); + // Your processing with the response body + } + + @Override + public void onFailure(Call call, IOException e) { + e.printStackTrace(); + } +}); +``` + +### 3.2 Get the user's drive + +To retrieve the user's drive: + +```java +Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me/drive").build(); + +client.newCall(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) throws IOException { + String responseBody = response.body().string(); + // Your processing with the response body + } + + @Override + public void onFailure(Call call, IOException e) { + e.printStackTrace(); + } +}); +``` + +## 4. Issues + +For known issues, see [issues](https://github.com/MicrosoftGraph/msgraph-sdk-java-core/issues). + +## 5. Contributions + +The Microsoft Graph SDK is open for contribution. To contribute to this project, see [Contributing](https://github.com/microsoftgraph/msgraph-sdk-java-core/blob/master/CONTRIBUTING.md). + + + +| [
Deepak Agrawal](https://github.com/deepak2016)
[:computer:](https://github.com/microsoftgraph/msgraph-sdk-java-core/commits?author=deepak2016 "Code") | [
Nakul Sabharwal][:computer:](https://github.com/microsoftgraph/msgraph-sdk-java-core/commits?author=NakulSabharwal "Code")
[](#question-NakulSabharwal "Answering Questions") [](https://github.com/microsoftgraph/msgraph-sdk-android-auth/commits?author=NakulSabharwal "Code") [](https://github.com/microsoftgraph/msgraph-sdk-android-auth/wiki "Documentation") [:clipboard:](#review-NakulSabharwal "Reviewed Pull Requests") [](https://github.com/microsoftgraph/msgraph-sdk-android-auth/commits?author=NakulSabharwal "Tests")
+| :---: | :---: | + + +This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind are welcome! + +## 6. Supported Java versions +The Microsoft Graph SDK for Java library is supported at runtime for Java 7+ and [Android API revision 15](http://source.android.com/source/build-numbers.html) and greater. + +## 7. License + +Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the [MIT license](LICENSE). + +## 8. Third-party notices + +[Third-party notices](THIRD%20PARTY%20NOTICES) diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..2b04dfc4e --- /dev/null +++ b/settings.gradle @@ -0,0 +1,18 @@ +/* + * This settings file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * In a single project build this file can be empty or even removed. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user guide at https://docs.gradle.org/4.3/userguide/multi_project_builds.html + */ + +/* +// To declare projects as part of a multi-project build use the 'include' method +include 'shared' +include 'api' +include 'services:webservice' +*/ + +rootProject.name = 'msgraph-sdk-java-core' diff --git a/src/main/java/com/microsoft/graph/content/MSBatchRequestContent.java b/src/main/java/com/microsoft/graph/content/MSBatchRequestContent.java new file mode 100644 index 000000000..5ffa80179 --- /dev/null +++ b/src/main/java/com/microsoft/graph/content/MSBatchRequestContent.java @@ -0,0 +1,149 @@ +package com.microsoft.graph.content; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import okhttp3.Headers; +import okhttp3.Request; +import okhttp3.RequestBody; +import okio.Buffer; + +public class MSBatchRequestContent { + private Map batchRequestStepsHashMap; + + // Maximum number of requests that can be sent in a batch + public static final int MAX_NUMBER_OF_REQUESTS = 20; + + /* + * Creates Batch request content using list provided + * + * @param batchRequestStepsArray List of batch steps for batching + */ + public MSBatchRequestContent(List batchRequestStepsArray) { + if(batchRequestStepsArray.size() > MAX_NUMBER_OF_REQUESTS) + throw new IllegalArgumentException("Number of batch request steps cannot exceed " + MAX_NUMBER_OF_REQUESTS); + + this.batchRequestStepsHashMap = new HashMap<>(); + for(MSBatchRequestStep requestStep: batchRequestStepsArray) + addBatchRequestStep(requestStep); + } + + /* + * Creates empty batch request content + */ + public MSBatchRequestContent() { + batchRequestStepsHashMap = new HashMap(); + } + + /* + * @param batchRequestStep Batch request step adding to batch content + * @return true or false based on addition or no addition of batch request step given + */ + public boolean addBatchRequestStep(MSBatchRequestStep batchRequestStep) { + if(batchRequestStepsHashMap.containsKey(batchRequestStep.getRequestId())) + return false; + batchRequestStepsHashMap.put(batchRequestStep.getRequestId(), batchRequestStep); + return true; + } + + /* + * @param requestId Id of Batch request step to be removed + * @return true or false based on removal or no removal of batch request step with given id + */ + public boolean removeBatchRequestStepWithId(String requestId) { + boolean removed = false; + if(batchRequestStepsHashMap.containsKey(requestId)) { + batchRequestStepsHashMap.remove(requestId); + removed = true; + for(Map.Entry steps : batchRequestStepsHashMap.entrySet()) { + if(steps.getValue() != null && steps.getValue().getArrayOfDependsOnIds() != null) { + while(steps.getValue().getArrayOfDependsOnIds().remove(requestId)); + } + } + } + return removed; + } + + /* + * @return Batch request content's json as String + */ + public String getBatchRequestContent() { + JSONObject batchRequestContentMap = new JSONObject(); + JSONArray batchContentArray = new JSONArray(); + for(Map.Entry requestStep : batchRequestStepsHashMap.entrySet()) { + batchContentArray.add(getBatchRequestObjectFromRequestStep(requestStep.getValue())); + } + batchRequestContentMap.put("requests", batchContentArray); + + String content = batchRequestContentMap.toString(); + return content; + } + + private JSONObject getBatchRequestObjectFromRequestStep(final MSBatchRequestStep batchRequestStep){ + JSONObject contentmap = new JSONObject(); + contentmap.put("id", batchRequestStep.getRequestId()); + + String url = batchRequestStep.getRequest().url().toString(); + url = url.replaceAll("https://graph.microsoft.com/v1.0/", ""); + url = url.replaceAll("http://graph.microsoft.com/v1.0/", ""); + url = url.replaceAll("https://graph.microsoft.com/beta/", ""); + url = url.replaceAll("http://graph.microsoft.com/beta/", ""); + contentmap.put("url", url); + + contentmap.put("method", batchRequestStep.getRequest().method().toString()); + + Headers headers = batchRequestStep.getRequest().headers(); + if(headers != null && headers.size() != 0) { + JSONObject headerMap = new JSONObject(); + for(Map.Entry> entry : headers.toMultimap().entrySet()) { + headerMap.put(entry.getKey(), getHeaderValuesAsString(entry.getValue())); + } + contentmap.put("headers", headerMap); + } + + List arrayOfDependsOnIds = batchRequestStep.getArrayOfDependsOnIds(); + if(arrayOfDependsOnIds != null) { + JSONArray array = new JSONArray(); + for(String dependsOnId : arrayOfDependsOnIds) array.add(dependsOnId); + contentmap.put("dependsOn", array); + } + + RequestBody body = batchRequestStep.getRequest().body(); + if(body != null) { + try { + contentmap.put("body", requestBodyToJSONObject(batchRequestStep.getRequest())); + }catch(IOException | ParseException e) { + e.printStackTrace(); + } + } + return contentmap; + } + + private String getHeaderValuesAsString(final List list) { + if(list == null || list.size() == 0)return ""; + StringBuilder builder = new StringBuilder(list.get(0)); + for(int i=1;i arrayOfDependsOnIds; + + public MSBatchRequestStep(String requestId, Request request, List arrayOfDependsOnIds) { + if(requestId == null) + throw new IllegalArgumentException("Request Id cannot be null."); + if(request == null) + new IllegalArgumentException("Request cannot be null."); + + this.requestId = requestId; + this.request = request; + this.arrayOfDependsOnIds = arrayOfDependsOnIds; + } + + public String getRequestId() { + return requestId; + } + + public Request getRequest() { + return request; + } + + public List getArrayOfDependsOnIds(){ + return arrayOfDependsOnIds; + } +} diff --git a/src/main/java/com/microsoft/graph/content/MSBatchResponseContent.java b/src/main/java/com/microsoft/graph/content/MSBatchResponseContent.java new file mode 100644 index 000000000..ed10d1da1 --- /dev/null +++ b/src/main/java/com/microsoft/graph/content/MSBatchResponseContent.java @@ -0,0 +1,188 @@ +package com.microsoft.graph.content; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; + +public class MSBatchResponseContent { + + private JSONObject batchResponseObj; + private Response batchResponse; + private Map batchRequestsHashMap; + private JSONArray batchResponseArray; + + /* + * @param batchResponse OkHttp batch response on execution of batch requests + */ + public MSBatchResponseContent(Response batchResponse) { + if(batchResponse == null) + throw new IllegalArgumentException("Batch Response cannot be null"); + + this.batchRequestsHashMap = createBatchRequestsHashMap(batchResponse); + this.batchResponse = batchResponse; + if(batchResponse.body() != null) { + try { + String batchResponseData = batchResponse.body().string(); + if(batchResponseData != null) { + batchResponseObj = stringToJSONObject(batchResponseData); + if(batchResponseObj != null) { + batchResponseArray = (JSONArray)batchResponseObj.get("responses"); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /* + * Returns OkHttp Response of given request Id + * + * @param requestId Request Id of batch step + * @return OkHttp Response corresponding to requestId + */ + public Response getResponseById(String requestId) { + if(batchResponseObj == null) + return null; + + JSONArray responses = (JSONArray)batchResponseObj.get("responses"); + if(responses == null) + return null; + + for(Object response : responses) { + JSONObject jsonresponse = (JSONObject)response; + String id = (String)jsonresponse.get("id"); + if(id.compareTo(requestId) == 0) { + Response.Builder builder = new Response.Builder(); + + // Put corresponding request into the constructed response + builder.request(batchRequestsHashMap.get(requestId)); + // copy protocol and message same as of batch response + builder.protocol(batchResponse.protocol()); + builder.message(batchResponse.message()); + + // Put status code of the corresponding request in JSONArray + if(jsonresponse.get("status") != null) { + Long status = (Long)jsonresponse.get("status"); + builder.code(status.intValue()); + } + + // Put body from response array for corresponding id into constructing response + if(jsonresponse.get("body") != null) { + JSONObject jsonObject = (JSONObject)jsonresponse.get("body"); + String bodyAsString = jsonObject.toJSONString(); + ResponseBody responseBody = ResponseBody.create(MediaType.parse("application/json; charset=utf-8"), bodyAsString); + builder.body(responseBody); + } + + // Put headers from response array for corresponding id into constructing response + if(jsonresponse.get("headers") != null){ + JSONObject jsonheaders = (JSONObject)jsonresponse.get("headers"); + for(Object key: jsonheaders.keySet()) { + String strkey = (String)key; + String strvalue = (String)jsonheaders.get(strkey); + for(String value : strvalue.split(";")) { + builder.header(strkey, value); + } + } + } + return builder.build(); + } + } + return null; + } + + /* + * @return responses as a string + */ + public String getResponses() { + return batchResponseArray != null ? batchResponseArray.toJSONString() : null; + } + + /* + * @return nextLink of batch response + */ + public String nextLink() { + if(batchResponseObj == null) return null; + Object nextLinkObject = batchResponseObj.get("nextLink"); + return nextLinkObject != null ? ((JSONObject)nextLinkObject).toString() : null; + } + + private Map createBatchRequestsHashMap(Response batchResponse) { + if(batchResponse == null)return null; + try { + Map batchRequestsHashMap = new HashMap<>(); + JSONObject requestJSONObject = requestBodyToJSONObject(batchResponse.request()); + JSONArray requestArray = (JSONArray)requestJSONObject.get("requests"); + for(Object item : requestArray) { + JSONObject requestObject = (JSONObject)item; + + Request.Builder builder = new Request.Builder(); + + if(requestObject.get("url") != null) { + StringBuilder fullUrl = new StringBuilder(batchResponse.request().url().toString().replace("$batch","")); + fullUrl.append(requestObject.get("url").toString()); + builder.url(fullUrl.toString()); + } + if(requestObject.get("headers") != null) { + JSONObject jsonheaders = (JSONObject)requestObject.get("headers"); + for(Object key: jsonheaders.keySet()) { + String strkey = (String)key; + String strvalue = (String)jsonheaders.get(strkey); + for(String value : strvalue.split("; ")) { + builder.header(strkey, value); + } + } + } + if(requestObject.get("body") != null) { + JSONObject jsonObject = (JSONObject)requestObject.get("body"); + String bodyAsString = jsonObject.toJSONString(); + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), bodyAsString); + builder.method(requestObject.get("method").toString(), requestBody); + } else { + builder.method(requestObject.get("method").toString(), null); + } + batchRequestsHashMap.put(requestObject.get("id").toString(), builder.build()); + } + return batchRequestsHashMap; + + } catch (IOException | ParseException e) { e.printStackTrace(); } + return null; + } + + private JSONObject stringToJSONObject(String input) { + JSONParser parser = new JSONParser(); + JSONObject jsonObject = null; + try { + if(input != null) { + jsonObject = (JSONObject) parser.parse(input); + } + } + catch(Exception e) { + e.printStackTrace(); + } + return jsonObject; + } + + private JSONObject requestBodyToJSONObject(final Request request) throws IOException, ParseException{ + if(request == null || request.body() == null)return null; + Request copy = request.newBuilder().build(); + Buffer buffer = new Buffer(); + copy.body().writeTo(buffer); + String requestBody = buffer.readUtf8(); + JSONObject jsonObject = (JSONObject)new JSONParser().parse(requestBody); + return jsonObject; + } +} diff --git a/src/main/java/com/microsoft/graph/httpcore/AuthenticationHandler.java b/src/main/java/com/microsoft/graph/httpcore/AuthenticationHandler.java new file mode 100644 index 000000000..42e9fe304 --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/AuthenticationHandler.java @@ -0,0 +1,28 @@ +package com.microsoft.graph.httpcore; + +import java.io.IOException; + +import com.microsoft.graph.httpcore.middlewareoption.MiddlewareType; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class AuthenticationHandler implements Interceptor { + + public final MiddlewareType MIDDLEWARE_TYPE = MiddlewareType.AUTHENTICATION; + + private ICoreAuthenticationProvider authProvider; + + public AuthenticationHandler(ICoreAuthenticationProvider authProvider) { + this.authProvider = authProvider; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request originalRequest = chain.request(); + Request authenticatedRequest = authProvider.authenticateRequest(originalRequest); + return chain.proceed(authenticatedRequest); + } + +} diff --git a/src/main/java/com/microsoft/graph/httpcore/HttpClients.java b/src/main/java/com/microsoft/graph/httpcore/HttpClients.java new file mode 100644 index 000000000..4084ae91d --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/HttpClients.java @@ -0,0 +1,35 @@ +package com.microsoft.graph.httpcore; + +import okhttp3.OkHttpClient; +import okhttp3.OkHttpClient.Builder; + +public class HttpClients { + private HttpClients() { + super(); + } + + /** + * Creates builder object for construction of custom + * {@link OkHttpClient} instances. + * + * @return OkHttpClient.Builder() custom builder for developer to add its own interceptors to it + */ + public static Builder custom() { + return new OkHttpClient.Builder(); + } + + /** + * Creates {@link OkHttpClient} instance with default + * configuration and provided authProvider + * + * @param auth Use IAuthenticationProvider instance provided while constructing http client + * @return OkHttpClient build with authentication provider given, default redirect and default retry handlers + */ + public static OkHttpClient createDefault(ICoreAuthenticationProvider auth) { + return new OkHttpClient.Builder().addInterceptor(new AuthenticationHandler(auth)) + .followRedirects(false) + .addInterceptor(new RetryHandler()) + .addInterceptor(new RedirectHandler()) + .build(); + } +} diff --git a/src/main/java/com/microsoft/graph/httpcore/ICoreAuthenticationProvider.java b/src/main/java/com/microsoft/graph/httpcore/ICoreAuthenticationProvider.java new file mode 100644 index 000000000..1ca39a432 --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/ICoreAuthenticationProvider.java @@ -0,0 +1,13 @@ +package com.microsoft.graph.httpcore; + +import okhttp3.Request; + +public interface ICoreAuthenticationProvider { + /** + * Authenticates the request + * + * @param request the request to authenticate + * @return Request with Authorization header added to it + */ + Request authenticateRequest(Request request); +} diff --git a/src/main/java/com/microsoft/graph/httpcore/RedirectHandler.java b/src/main/java/com/microsoft/graph/httpcore/RedirectHandler.java new file mode 100644 index 000000000..dbc866990 --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/RedirectHandler.java @@ -0,0 +1,124 @@ +package com.microsoft.graph.httpcore; + +import static java.net.HttpURLConnection.HTTP_MOVED_PERM; +import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; +import static java.net.HttpURLConnection.HTTP_SEE_OTHER; +import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; +import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; + +import java.io.IOException; +import java.net.ProtocolException; + +import com.microsoft.graph.httpcore.middlewareoption.MiddlewareType; +import com.microsoft.graph.httpcore.middlewareoption.RedirectOptions; + +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class RedirectHandler implements Interceptor{ + + public final MiddlewareType MIDDLEWARE_TYPE = MiddlewareType.REDIRECT; + + private RedirectOptions mRedirectOptions; + + /* + * Initialize using default redirect options, default IShouldRedirect and max redirect value + */ + public RedirectHandler() { + this(null); + } + + /* + * @param redirectOptions pass instance of redirect options to be used + */ + public RedirectHandler(RedirectOptions redirectOptions) { + this.mRedirectOptions = redirectOptions; + if(redirectOptions == null) { + this.mRedirectOptions = new RedirectOptions(); + } + } + + boolean isRedirected(Request request, Response response, int redirectCount, RedirectOptions redirectOptions) throws IOException { + // Check max count of redirects reached + if(redirectCount > redirectOptions.maxRedirects()) return false; + + // Location header empty then don't redirect + final String locationHeader = response.header("location"); + if(locationHeader == null) + return false; + + // If any of 301,302,303,307,308 then redirect + final int statusCode = response.code(); + if(statusCode == HTTP_PERM_REDIRECT || //308 + statusCode == HTTP_MOVED_PERM || //301 + statusCode == HTTP_TEMP_REDIRECT || //307 + statusCode == HTTP_SEE_OTHER || //303 + statusCode == HTTP_MOVED_TEMP) //302 + return true; + + return false; + } + + Request getRedirect( + final Request request, + final Response userResponse) throws ProtocolException { + String location = userResponse.header("Location"); + if (location == null) return null; + + // TODO: If Location header is relative reference then final URL should be relative to original target URL. + + HttpUrl requestUrl = userResponse.request().url(); + + HttpUrl locationUrl = userResponse.request().url().resolve(location); + + // Don't follow redirects to unsupported protocols. + if (locationUrl == null) return null; + + // Most redirects don't include a request body. + Request.Builder requestBuilder = userResponse.request().newBuilder(); + + // When redirecting across hosts, drop all authentication headers. This + // is potentially annoying to the application layer since they have no + // way to retain them. + boolean sameScheme = locationUrl.scheme().equalsIgnoreCase(requestUrl.scheme()); + boolean sameHost = locationUrl.host().toString().equalsIgnoreCase(requestUrl.host().toString()); + if (!sameScheme || !sameHost) { + requestBuilder.removeHeader("Authorization"); + } + + // Response status code 303 See Other then POST changes to GET + if(userResponse.code() == HTTP_SEE_OTHER) { + requestBuilder.method("GET", null); + } + + return requestBuilder.url(locationUrl).build(); + } + + // Intercept request and response made to network + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Response response = null; + int requestsCount = 1; + + // Use should retry pass along with this request + RedirectOptions redirectOptions = request.tag(RedirectOptions.class); + redirectOptions = redirectOptions != null ? redirectOptions : this.mRedirectOptions; + + while(true) { + response = chain.proceed(request); + boolean shouldRedirect = isRedirected(request, response, requestsCount, redirectOptions) + && redirectOptions.shouldRedirect().shouldRedirect(response); + if(!shouldRedirect) break; + + Request followup = getRedirect(request, response); + if(followup == null) break; + request = followup; + + requestsCount++; + } + return response; + } +} diff --git a/src/main/java/com/microsoft/graph/httpcore/RetryHandler.java b/src/main/java/com/microsoft/graph/httpcore/RetryHandler.java new file mode 100644 index 000000000..5d8b18fc8 --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/RetryHandler.java @@ -0,0 +1,142 @@ +package com.microsoft.graph.httpcore; + +import java.io.IOException; + +import com.microsoft.graph.httpcore.middlewareoption.IShouldRetry; +import com.microsoft.graph.httpcore.middlewareoption.MiddlewareType; +import com.microsoft.graph.httpcore.middlewareoption.RetryOptions; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class RetryHandler implements Interceptor{ + + public final MiddlewareType MIDDLEWARE_TYPE = MiddlewareType.RETRY; + + private RetryOptions mRetryOption; + + /* + * constant string being used + */ + private final String RETRY_ATTEMPT_HEADER = "Retry-Attempt"; + private final String RETRY_AFTER = "Retry-After"; + private final String TRANSFER_ENCODING = "Transfer-Encoding"; + private final String TRANSFER_ENCODING_CHUNKED = "chunked"; + private final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + private final String CONTENT_TYPE = "Content-Type"; + + public static final int MSClientErrorCodeTooManyRequests = 429; + public static final int MSClientErrorCodeServiceUnavailable = 503; + public static final int MSClientErrorCodeGatewayTimeout = 504; + + private final long DELAY_MILLISECONDS = 1000; + + /* + * @retryOption Create Retry handler using retry option + */ + public RetryHandler(RetryOptions retryOption) { + this.mRetryOption = retryOption; + if(this.mRetryOption == null) { + this.mRetryOption = new RetryOptions(); + } + } + /* + * Initialize retry handler with default retry option + */ + public RetryHandler() { + this(null); + } + + boolean retryRequest(Response response, int executionCount, Request request, RetryOptions retryOptions) { + + // Should retry option + // Use should retry common for all requests + IShouldRetry shouldRetryCallback = null; + if(retryOptions != null) { + shouldRetryCallback = retryOptions.shouldRetry(); + } + + boolean shouldRetry = false; + // Status codes 429 503 504 + int statusCode = response.code(); + // Only requests with payloads that are buffered/rewindable are supported. + // Payloads with forward only streams will be have the responses returned + // without any retry attempt. + shouldRetry = + (executionCount <= retryOptions.maxRetries()) + && checkStatus(statusCode) && isBuffered(response, request) + && shouldRetryCallback != null + && shouldRetryCallback.shouldRetry(retryOptions.delay(), executionCount, request, response); + + if(shouldRetry) { + long retryInterval = getRetryAfter(response, retryOptions.delay(), executionCount); + try { + Thread.sleep(retryInterval); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return shouldRetry; + } + + long getRetryAfter(Response response, long delay, int executionCount) { + String retryAfterHeader = response.header(RETRY_AFTER); + long retryDelay = RetryOptions.DEFAULT_DELAY; + if(retryAfterHeader != null) { + retryDelay = Long.parseLong(retryAfterHeader); + } else { + retryDelay = (long)((Math.pow(2.0, (double)executionCount)-1)*0.5); + retryDelay = executionCount < 2 ? retryDelay : retryDelay + delay + (long)Math.random(); + retryDelay *= DELAY_MILLISECONDS; + } + return Math.min(retryDelay, RetryOptions.MAX_DELAY); + } + + boolean checkStatus(int statusCode) { + return (statusCode == MSClientErrorCodeTooManyRequests || statusCode == MSClientErrorCodeServiceUnavailable + || statusCode == MSClientErrorCodeGatewayTimeout); + } + + boolean isBuffered(Response response, Request request) { + String methodName = request.method(); + if(methodName.equalsIgnoreCase("GET") || methodName.equalsIgnoreCase("DELETE") || methodName.equalsIgnoreCase("HEAD") || methodName.equalsIgnoreCase("OPTIONS")) + return true; + + boolean isHTTPMethodPutPatchOrPost = methodName.equalsIgnoreCase("POST") || + methodName.equalsIgnoreCase("PUT") || + methodName.equalsIgnoreCase("PATCH"); + + if(isHTTPMethodPutPatchOrPost) { + boolean isStream = response.header(CONTENT_TYPE)!=null && response.header(CONTENT_TYPE).equalsIgnoreCase(APPLICATION_OCTET_STREAM); + if(!isStream) { + String transferEncoding = response.header(TRANSFER_ENCODING); + boolean isTransferEncodingChunked = (transferEncoding != null) && + transferEncoding.equalsIgnoreCase(TRANSFER_ENCODING_CHUNKED); + + if(request.body() != null && isTransferEncodingChunked) + return true; + } + } + return false; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + Response response = chain.proceed(request); + + // Use should retry pass along with this request + RetryOptions retryOption = request.tag(RetryOptions.class); + retryOption = retryOption != null ? retryOption : mRetryOption; + + int executionCount = 1; + while(retryRequest(response, executionCount, request, retryOption)) { + request = request.newBuilder().addHeader(RETRY_ATTEMPT_HEADER, String.valueOf(executionCount)).build(); + executionCount++; + response = chain.proceed(request); + } + return response; + } + +} diff --git a/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IMiddlewareControl.java b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IMiddlewareControl.java new file mode 100644 index 000000000..699c8d152 --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IMiddlewareControl.java @@ -0,0 +1,5 @@ +package com.microsoft.graph.httpcore.middlewareoption; + +public interface IMiddlewareControl { + +} diff --git a/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IShouldRedirect.java b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IShouldRedirect.java new file mode 100644 index 000000000..8ca9cd545 --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IShouldRedirect.java @@ -0,0 +1,7 @@ +package com.microsoft.graph.httpcore.middlewareoption; + +import okhttp3.Response; + +public interface IShouldRedirect { + boolean shouldRedirect(final Response response); +} diff --git a/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IShouldRetry.java b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IShouldRetry.java new file mode 100644 index 000000000..249a5ba3e --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/IShouldRetry.java @@ -0,0 +1,8 @@ +package com.microsoft.graph.httpcore.middlewareoption; + +import okhttp3.Request; +import okhttp3.Response; + +public interface IShouldRetry { + boolean shouldRetry(long delay, int executionCount, Request request,Response response); +} diff --git a/src/main/java/com/microsoft/graph/httpcore/middlewareoption/MiddlewareType.java b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/MiddlewareType.java new file mode 100644 index 000000000..193ab9d1b --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/MiddlewareType.java @@ -0,0 +1,16 @@ +package com.microsoft.graph.httpcore.middlewareoption; + +public enum MiddlewareType { + + //Authentication Middleware + AUTHENTICATION, + + //Redirect Middleware + REDIRECT, + + //Retry Middleware + RETRY, + + //Not supported + NOT_SUPPORTED +} diff --git a/src/main/java/com/microsoft/graph/httpcore/middlewareoption/RedirectOptions.java b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/RedirectOptions.java new file mode 100644 index 000000000..f4ef54900 --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/RedirectOptions.java @@ -0,0 +1,52 @@ +package com.microsoft.graph.httpcore.middlewareoption; + +import okhttp3.Response; + +public class RedirectOptions implements IMiddlewareControl{ + private int maxRedirects; + public static final int DEFAULT_MAX_REDIRECTS = 5; + public static final int MAX_REDIRECTS = 20; + + private IShouldRedirect shouldRedirect; + public static final IShouldRedirect DEFAULT_SHOULD_REDIRECT = new IShouldRedirect() { + @Override + public boolean shouldRedirect(Response response) { + return true; + } + }; + + /* + * Create default instance of redirect options, with default values of max redirects and should redirect + */ + public RedirectOptions() { + this(DEFAULT_MAX_REDIRECTS, DEFAULT_SHOULD_REDIRECT); + } + + /* + * @param maxRedirects Max redirects to occur + * @param shouldRedirect Should redirect callback called before every redirect + */ + public RedirectOptions(int maxRedirects, IShouldRedirect shouldRedirect) { + if(maxRedirects < 0) + throw new IllegalArgumentException("Max redirects cannot be negative"); + if(maxRedirects > MAX_REDIRECTS) + throw new IllegalArgumentException("Max redirect cannot exceed " + MAX_REDIRECTS); + + this.maxRedirects = maxRedirects; + this.shouldRedirect = shouldRedirect != null ? shouldRedirect : DEFAULT_SHOULD_REDIRECT; + } + + /* + * @return max redirects + */ + public int maxRedirects() { + return this.maxRedirects; + } + + /* + * @return should redirect + */ + public IShouldRedirect shouldRedirect() { + return this.shouldRedirect; + } +} diff --git a/src/main/java/com/microsoft/graph/httpcore/middlewareoption/RetryOptions.java b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/RetryOptions.java new file mode 100644 index 000000000..9010122f5 --- /dev/null +++ b/src/main/java/com/microsoft/graph/httpcore/middlewareoption/RetryOptions.java @@ -0,0 +1,73 @@ +package com.microsoft.graph.httpcore.middlewareoption; + +import okhttp3.Request; +import okhttp3.Response; + +public class RetryOptions implements IMiddlewareControl { + private IShouldRetry mShouldretry; + public static final IShouldRetry DEFAULT_SHOULD_RETRY = new IShouldRetry() { + @Override + public boolean shouldRetry(long delay, int executionCount, Request request, Response response) { + return true; + } + }; + + private int mMaxRetries; + public static final int MAX_RETRIES = 10; + public static final int DEFAULT_MAX_RETRIES = 3; + + /* + * Delay in seconds + */ + private long mDelay; + public static final long DEFAULT_DELAY = 3; // 3 seconds default delay + public static final long MAX_DELAY = 180; // 180 second max delay + + /* + * Create default instance of retry options, with default values of delay, max retries and shouldRetry callback. + */ + public RetryOptions(){ + this(DEFAULT_SHOULD_RETRY, DEFAULT_MAX_RETRIES, DEFAULT_DELAY); + } + + /* + * @param shouldRetry Retry callback to be called before making a retry + * @param maxRetries Number of max retires for a request + * @param delay Delay in seconds between retries + */ + public RetryOptions(IShouldRetry shouldRetry, int maxRetries, long delay) { + if(delay > MAX_DELAY) + throw new IllegalArgumentException("Delay cannot exceed " + MAX_DELAY); + if(delay < 0) + throw new IllegalArgumentException("Delay cannot be negative"); + if(maxRetries > MAX_RETRIES) + throw new IllegalArgumentException("Max retries cannot exceed " + MAX_RETRIES); + if(maxRetries < 0) + throw new IllegalArgumentException("Max retries cannot be negative"); + + this.mShouldretry = shouldRetry != null ? shouldRetry : DEFAULT_SHOULD_RETRY; + this.mMaxRetries = maxRetries; + this.mDelay = delay; + } + + /* + * @return should retry callback + */ + public IShouldRetry shouldRetry() { + return mShouldretry; + } + + /* + * @return Number of max retries + */ + public int maxRetries() { + return mMaxRetries; + } + + /* + * @return Delay in seconds between retries + */ + public long delay() { + return mDelay; + } +} diff --git a/src/test/java/com/microsoft/graph/content/MSBatchRequestContentTest.java b/src/test/java/com/microsoft/graph/content/MSBatchRequestContentTest.java new file mode 100644 index 000000000..ef5835654 --- /dev/null +++ b/src/test/java/com/microsoft/graph/content/MSBatchRequestContentTest.java @@ -0,0 +1,88 @@ +package com.microsoft.graph.content; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import okhttp3.Request; + +public class MSBatchRequestContentTest { + + String testurl = "http://graph.microsoft.com/me"; + + @Test + public void testMSBatchRequestContentCreation() { + List requestStepArray = new ArrayList<>(); + for(int i=0;i<5;i++) { + Request request = new Request.Builder().url(testurl).build(); + List arrayOfDependsOnIds = new ArrayList<>(); + MSBatchRequestStep requestStep = new MSBatchRequestStep("" + i, request, arrayOfDependsOnIds); + requestStepArray.add(requestStep); + } + MSBatchRequestContent requestContent = new MSBatchRequestContent(requestStepArray); + assertTrue(requestContent.getBatchRequestContent() != null); + } + + @Test + public void testGetBatchRequestContent() { + Request request = new Request.Builder().url(testurl).build(); + List arrayOfDependsOnIds = new ArrayList<>(); + MSBatchRequestStep requestStep = new MSBatchRequestStep("1", request, arrayOfDependsOnIds); + MSBatchRequestContent requestContent = new MSBatchRequestContent(); + requestContent.addBatchRequestStep(requestStep); + String content = requestContent.getBatchRequestContent(); + String expectedContent = "{\"requests\":[{\"method\":\"GET\",\"dependsOn\":[],\"id\":\"1\",\"url\":\"http:\\/\\/graph.microsoft.com\\/me\"}]}"; + assertTrue(content.compareTo(expectedContent) == 0); + } + + @Test + public void testGetBatchRequestContentWithHeader() { + Request request = new Request.Builder().url(testurl).header("testkey", "testvalue").build(); + List arrayOfDependsOnIds = new ArrayList<>(); + MSBatchRequestStep requestStep = new MSBatchRequestStep("1", request, arrayOfDependsOnIds); + MSBatchRequestContent requestContent = new MSBatchRequestContent(); + requestContent.addBatchRequestStep(requestStep); + String content = requestContent.getBatchRequestContent(); + System.out.println(content); + String expectedContent = "{\"requests\":[{\"headers\":{\"testkey\":\"testvalue\"},\"method\":\"GET\",\"dependsOn\":[],\"id\":\"1\",\"url\":\"http:\\/\\/graph.microsoft.com\\/me\"}]}"; + assertTrue(content.compareTo(expectedContent) == 0); + } + + @Test + public void testRemoveBatchRequesStepWithId() { + Request request = new Request.Builder().url(testurl).build(); + List arrayOfDependsOnIds = new ArrayList<>(); + MSBatchRequestStep requestStep = new MSBatchRequestStep("1", request, arrayOfDependsOnIds); + MSBatchRequestContent requestContent = new MSBatchRequestContent(); + requestContent.addBatchRequestStep(requestStep); + requestContent.removeBatchRequestStepWithId("1"); + String content = requestContent.getBatchRequestContent(); + String expectedContent = "{\"requests\":[]}"; + assertTrue(content.compareTo(expectedContent) == 0); + } + + @Test + public void testRemoveBatchRequesStepWithIdByAddingMultipleBatchSteps() { + Request request = new Request.Builder().url(testurl).build(); + List arrayOfDependsOnIds = new ArrayList<>(); + MSBatchRequestStep requestStep = new MSBatchRequestStep("1", request, arrayOfDependsOnIds); + + Request request1 = new Request.Builder().url(testurl).build(); + List arrayOfDependsOnIds1 = new ArrayList<>(); + arrayOfDependsOnIds1.add("1"); + MSBatchRequestStep requestStep1 = new MSBatchRequestStep("2", request1, arrayOfDependsOnIds1); + + MSBatchRequestContent requestContent = new MSBatchRequestContent(); + requestContent.addBatchRequestStep(requestStep); + requestContent.addBatchRequestStep(requestStep1); + + requestContent.removeBatchRequestStepWithId("1"); + String content = requestContent.getBatchRequestContent(); + String expectedContent = "{\"requests\":[{\"method\":\"GET\",\"dependsOn\":[],\"id\":\"2\",\"url\":\"http:\\/\\/graph.microsoft.com\\/me\"}]}"; + assertTrue(content.compareTo(expectedContent) == 0); + } + +} diff --git a/src/test/java/com/microsoft/graph/content/MSBatchRequestStepTest.java b/src/test/java/com/microsoft/graph/content/MSBatchRequestStepTest.java new file mode 100644 index 000000000..d17af87a2 --- /dev/null +++ b/src/test/java/com/microsoft/graph/content/MSBatchRequestStepTest.java @@ -0,0 +1,25 @@ +package com.microsoft.graph.content; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import okhttp3.Request; + +public class MSBatchRequestStepTest { + + @Test + public void testMSBatchRequestStepCreation() { + Request request = new Request.Builder().url("http://graph.microsoft.com").build(); + List arrayOfDependsOnIds = new ArrayList<>(); + MSBatchRequestStep requestStep = new MSBatchRequestStep("1", request, arrayOfDependsOnIds); + assertTrue("Test BatchRequestStep creation", requestStep != null); + assertTrue("Test Request id", requestStep.getRequestId().compareTo("1") == 0); + assertTrue("Test Request object", requestStep.getRequest() == request); + assertTrue("Test Array of depends on Ids", requestStep.getArrayOfDependsOnIds() != null); + } + +} diff --git a/src/test/java/com/microsoft/graph/content/MSBatchResponseContentTest.java b/src/test/java/com/microsoft/graph/content/MSBatchResponseContentTest.java new file mode 100644 index 000000000..87ac0ae7e --- /dev/null +++ b/src/test/java/com/microsoft/graph/content/MSBatchResponseContentTest.java @@ -0,0 +1,86 @@ +package com.microsoft.graph.content; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.junit.Test; + +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class MSBatchResponseContentTest { + + @Test + public void testNullMSBatchResponseContent() { + Response response = null; + try { + new MSBatchResponseContent(response); + } + catch(IllegalArgumentException e) { + assertNotNull(e); + } + } + + @Test + public void testValidMSBatchResponseContent() { + String responsebody = "{\"responses\": [{\"id\": \"1\",\"status\":200,\"headers\" : {\"Cache-Control\":\"no-cache\",\"OData-Version\":\"4.0\",\"Content-Type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"},\"body\":{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users/$entity\",\"businessPhones\":[\"8006427676\"],\"displayName\":\"MOD Administrator\",\"givenName\":\"MOD\",\"jobTitle\":null,\"mail\":\"admin@M365x751487.OnMicrosoft.com\",\"mobilePhone\":\"425-882-1032\",\"officeLocation\":null,\"preferredLanguage\":\"en-US\",\"surname\":\"Administrator\",\"userPrincipalName\":\"admin@M365x751487.onmicrosoft.com\",\"id\":\"6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c\"}},{\"id\": \"2\",\"status\":200,\"headers\" : {\"Cache-Control\":\"no-store, no-cache\",\"OData-Version\":\"4.0\",\"Content-Type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"},\"body\":{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#drives/$entity\",\"createdDateTime\":\"2019-01-12T09:05:38Z\",\"description\":\"\",\"id\":\"b!nlu9o5I9g0y8gsHXfUM_bPTZ0oM_wVNArHM5R4-VkHLlnxx5SpqHRJledwfICP9f\",\"lastModifiedDateTime\":\"2019-03-06T06:59:04Z\",\"name\":\"OneDrive\",\"webUrl\":\"https://m365x751487-my.sharepoint.com/personal/admin_m365x751487_onmicrosoft_com/Documents\",\"driveType\":\"business\",\"createdBy\":{\"user\":{\"displayName\":\"System Account\"}},\"lastModifiedBy\":{\"user\":{\"displayName\":\"System Account\"}},\"owner\":{\"user\":{\"email\":\"admin@M365x751487.OnMicrosoft.com\",\"id\":\"6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c\",\"displayName\":\"MOD Administrator\"}},\"quota\":{\"deleted\":0,\"remaining\":1099509670098,\"state\":\"normal\",\"total\":1099511627776,\"used\":30324}}},{\"id\": \"3\",\"status\":201,\"headers\" : {\"Location\":\"https://graph.microsoft.com/v1.0/users/6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c/onenote/notebooks/1-94e4376a-a1c1-441a-8b41-af5c86ee39d0\",\"Preference-Applied\":\"odata.include-annotations=*\",\"Cache-Control\":\"no-cache\",\"OData-Version\":\"4.0\",\"Content-Type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"},\"body\":{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c')/onenote/notebooks/$entity\",\"id\":\"1-94e4376a-a1c1-441a-8b41-af5c86ee39d0\",\"self\":\"https://graph.microsoft.com/v1.0/users/6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c/onenote/notebooks/1-94e4376a-a1c1-441a-8b41-af5c86ee39d0\",\"createdDateTime\":\"2019-03-06T08:08:09Z\",\"displayName\":\"My Notebook -442293399\",\"lastModifiedDateTime\":\"2019-03-06T08:08:09Z\",\"isDefault\":false,\"userRole\":\"Owner\",\"isShared\":false,\"sectionsUrl\":\"https://graph.microsoft.com/v1.0/users/6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c/onenote/notebooks/1-94e4376a-a1c1-441a-8b41-af5c86ee39d0/sections\",\"sectionGroupsUrl\":\"https://graph.microsoft.com/v1.0/users/6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c/onenote/notebooks/1-94e4376a-a1c1-441a-8b41-af5c86ee39d0/sectionGroups\",\"createdBy\":{\"user\":{\"id\":\"6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c\",\"displayName\":\"MOD Administrator\"}},\"lastModifiedBy\":{\"user\":{\"id\":\"6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c\",\"displayName\":\"MOD Administrator\"}},\"links\":{\"oneNoteClientUrl\":{\"href\":\"onenote:https://m365x751487-my.sharepoint.com/personal/admin_m365x751487_onmicrosoft_com/Documents/Notebooks/My%20Notebook%20-442293399\"},\"oneNoteWebUrl\":{\"href\":\"https://m365x751487-my.sharepoint.com/personal/admin_m365x751487_onmicrosoft_com/Documents/Notebooks/My%20Notebook%20-442293399\"}}}}]}"; + String requestbody = "{\"requests\":[{\"method\":\"GET\",\"dependsOn\":[],\"id\":\"1\",\"url\":\"me\"},{\"method\":\"GET\",\"dependsOn\":[],\"id\":\"2\",\"url\":\"me\\/drive\"},{\"headers\":{\"content-type\":\"application\\/json\"},\"method\":\"POST\",\"dependsOn\":[],\"id\":\"3\",\"body\":{\"displayName\":\"My Notebook -1263732088\"},\"url\":\"me\\/onenote\\/notebooks\"}]}"; + Response responsedata = TestResponse(responsebody, requestbody); + MSBatchResponseContent batchresponse = new MSBatchResponseContent(responsedata); + assertTrue(batchresponse.getResponses() != null); + } + + @Test + public void testInvalidMSBatchResponseContentWithEmptyResponse() { + String responsebody = "{\"responses\": [] }"; + String requestbody = "{\"requests\":[]}"; + Response responsedata = TestResponse(responsebody, requestbody); + MSBatchResponseContent batchresponse = new MSBatchResponseContent(responsedata); + assertTrue(batchresponse.getResponseById("1") == null); + } + + @Test + public void testInvalidMSBatchResponseContentWithNullResponseString() { + try{ + new MSBatchResponseContent(null); + }catch(Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testInvalidMSBatchResponseContentWithMalformedResponse() { + String invalidResponsebody = "{responses: [] }"; + String requestbody = "{requests:[]}"; + Response invalidResponsedata = TestResponse(invalidResponsebody, requestbody); + MSBatchResponseContent batchresponse = new MSBatchResponseContent(invalidResponsedata); + assertTrue(batchresponse.getResponses() == null); + } + + @Test + public void testGetMSBatchResponseContentByID() throws IOException { + String responsebody = "{\"responses\": [{\"id\": \"1\",\"status\":200,\"headers\" : {\"Cache-Control\":\"no-cache\",\"OData-Version\":\"4.0\",\"Content-Type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"},\"body\":{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users/$entity\",\"businessPhones\":[\"8006427676\"],\"displayName\":\"MOD Administrator\",\"givenName\":\"MOD\",\"jobTitle\":null,\"mail\":\"admin@M365x751487.OnMicrosoft.com\",\"mobilePhone\":\"425-882-1032\",\"officeLocation\":null,\"preferredLanguage\":\"en-US\",\"surname\":\"Administrator\",\"userPrincipalName\":\"admin@M365x751487.onmicrosoft.com\",\"id\":\"6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c\"}},{\"id\": \"2\",\"status\":200,\"headers\" : {\"Cache-Control\":\"no-store, no-cache\",\"OData-Version\":\"4.0\",\"Content-Type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"},\"body\":{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#drives/$entity\",\"createdDateTime\":\"2019-01-12T09:05:38Z\",\"description\":\"\",\"id\":\"b!nlu9o5I9g0y8gsHXfUM_bPTZ0oM_wVNArHM5R4-VkHLlnxx5SpqHRJledwfICP9f\",\"lastModifiedDateTime\":\"2019-03-06T06:59:04Z\",\"name\":\"OneDrive\",\"webUrl\":\"https://m365x751487-my.sharepoint.com/personal/admin_m365x751487_onmicrosoft_com/Documents\",\"driveType\":\"business\",\"createdBy\":{\"user\":{\"displayName\":\"System Account\"}},\"lastModifiedBy\":{\"user\":{\"displayName\":\"System Account\"}},\"owner\":{\"user\":{\"email\":\"admin@M365x751487.OnMicrosoft.com\",\"id\":\"6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c\",\"displayName\":\"MOD Administrator\"}},\"quota\":{\"deleted\":0,\"remaining\":1099509670098,\"state\":\"normal\",\"total\":1099511627776,\"used\":30324}}},{\"id\": \"3\",\"status\":201,\"headers\" : {\"Location\":\"https://graph.microsoft.com/v1.0/users/6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c/onenote/notebooks/1-94e4376a-a1c1-441a-8b41-af5c86ee39d0\",\"Preference-Applied\":\"odata.include-annotations=*\",\"Cache-Control\":\"no-cache\",\"OData-Version\":\"4.0\",\"Content-Type\":\"application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8\"},\"body\":{\"@odata.context\":\"https://graph.microsoft.com/v1.0/$metadata#users('6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c')/onenote/notebooks/$entity\",\"id\":\"1-94e4376a-a1c1-441a-8b41-af5c86ee39d0\",\"self\":\"https://graph.microsoft.com/v1.0/users/6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c/onenote/notebooks/1-94e4376a-a1c1-441a-8b41-af5c86ee39d0\",\"createdDateTime\":\"2019-03-06T08:08:09Z\",\"displayName\":\"My Notebook -442293399\",\"lastModifiedDateTime\":\"2019-03-06T08:08:09Z\",\"isDefault\":false,\"userRole\":\"Owner\",\"isShared\":false,\"sectionsUrl\":\"https://graph.microsoft.com/v1.0/users/6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c/onenote/notebooks/1-94e4376a-a1c1-441a-8b41-af5c86ee39d0/sections\",\"sectionGroupsUrl\":\"https://graph.microsoft.com/v1.0/users/6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c/onenote/notebooks/1-94e4376a-a1c1-441a-8b41-af5c86ee39d0/sectionGroups\",\"createdBy\":{\"user\":{\"id\":\"6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c\",\"displayName\":\"MOD Administrator\"}},\"lastModifiedBy\":{\"user\":{\"id\":\"6b4fa8ea-7e6e-486e-a8f4-d00a5b23488c\",\"displayName\":\"MOD Administrator\"}},\"links\":{\"oneNoteClientUrl\":{\"href\":\"onenote:https://m365x751487-my.sharepoint.com/personal/admin_m365x751487_onmicrosoft_com/Documents/Notebooks/My%20Notebook%20-442293399\"},\"oneNoteWebUrl\":{\"href\":\"https://m365x751487-my.sharepoint.com/personal/admin_m365x751487_onmicrosoft_com/Documents/Notebooks/My%20Notebook%20-442293399\"}}}}]}"; + String requestbody = "{\"requests\":[{\"method\":\"GET\",\"dependsOn\":[],\"id\":\"1\",\"url\":\"me\"},{\"method\":\"GET\",\"dependsOn\":[],\"id\":\"2\",\"url\":\"me\\/drive\"},{\"headers\":{\"content-type\":\"application\\/json\"},\"method\":\"POST\",\"dependsOn\":[],\"id\":\"3\",\"body\":{\"displayName\":\"My Notebook -1263732088\"},\"url\":\"me\\/onenote\\/notebooks\"}]}"; + Response responsedata = TestResponse(responsebody,requestbody); + MSBatchResponseContent batchresponse = new MSBatchResponseContent(responsedata); + Response response = batchresponse.getResponseById("1"); + assertTrue(response != null); + } + + private Response TestResponse(String responsebody, String requestbody) { + Response.Builder builder = new Response.Builder(); + builder.body(ResponseBody.create(MediaType.parse("application/json"), responsebody)); + Request request = new Request.Builder().url("https://graph.microsoft.com/").method("POST", RequestBody.create(MediaType.parse("application/json"), requestbody)).build(); + builder.request(request); + builder.protocol(Protocol.HTTP_1_0); + builder.code(200); + builder.message("test message"); + return builder.build(); + } +} diff --git a/src/test/java/com/microsoft/graph/httpcore/AuthenticationHandlerTest.java b/src/test/java/com/microsoft/graph/httpcore/AuthenticationHandlerTest.java new file mode 100644 index 000000000..7f5feba6f --- /dev/null +++ b/src/test/java/com/microsoft/graph/httpcore/AuthenticationHandlerTest.java @@ -0,0 +1,45 @@ +package com.microsoft.graph.httpcore; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Ignore; +import org.junit.Test; + +import okhttp3.Request; +import okhttp3.internal.http.RealInterceptorChain; + +@Ignore +public class AuthenticationHandlerTest { + + static String token = "TEST-TOKEN"; + + public static class AuthProvider implements ICoreAuthenticationProvider{ + public Request authenticateRequest(Request request) { + Request newRequest = request.newBuilder().addHeader("Authorization", "Bearer " + token).build(); + return newRequest; + } + } + + @Test + public void testAuthenticationHandler() { + AuthProvider authProvider = new AuthProvider(); + AuthenticationHandler authHandler = new AuthenticationHandler(authProvider); + Request request = new Request.Builder().url("https://graph.microsoft.com/v1.0/me/").build(); + + RealInterceptorChain chain = new RealInterceptorChain(null, null, null, null, 0, request, null, null, 0, 0, 0); + + try { + authHandler.intercept(chain); + + String value = request.header("Authorization"); + assertTrue(value.equals("Bearer " + token)); + + + } catch (Exception e) { + e.printStackTrace(); + fail("Authentication handler failure"); + } + } + +} diff --git a/src/test/java/com/microsoft/graph/httpcore/HttpClientsTest.java b/src/test/java/com/microsoft/graph/httpcore/HttpClientsTest.java new file mode 100644 index 000000000..85bb76fd5 --- /dev/null +++ b/src/test/java/com/microsoft/graph/httpcore/HttpClientsTest.java @@ -0,0 +1,25 @@ +package com.microsoft.graph.httpcore; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import okhttp3.OkHttpClient; +import okhttp3.Request; + +public class HttpClientsTest { + + @Test + public void testHttpClientCreation() { + ICoreAuthenticationProvider authprovider = new ICoreAuthenticationProvider() { + public Request authenticateRequest(Request request) { + Request newRequest = request.newBuilder().addHeader("Authorization", "Bearer " + "TOKEN").build(); + return newRequest; + } + }; + + OkHttpClient httpclient = HttpClients.createDefault(authprovider); + assertTrue(httpclient != null); + } + +} diff --git a/src/test/java/com/microsoft/graph/httpcore/RedirectHandlerTest.java b/src/test/java/com/microsoft/graph/httpcore/RedirectHandlerTest.java new file mode 100644 index 000000000..3182b850f --- /dev/null +++ b/src/test/java/com/microsoft/graph/httpcore/RedirectHandlerTest.java @@ -0,0 +1,232 @@ +package com.microsoft.graph.httpcore; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.ProtocolException; + +import org.junit.Test; + +import com.microsoft.graph.httpcore.middlewareoption.RedirectOptions; + +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.internal.http.StatusLine; + +public class RedirectHandlerTest { + + String testmeurl = "https://graph.microsoft.com/v1.0/me/"; + String testurl = "https://graph.microsoft.com/v1.0/"; + String differenthosturl = "https://graph.abc.com/v1.0/"; + + @Test + public void testIsRedirectedFailureByNoLocationHeader() throws IOException { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_MOVED_TEMP) + .message("Moved Temporarily") + .request(httpget) + .build(); + boolean isRedirected = redirectHandler.isRedirected(httpget, response, 0, new RedirectOptions()); + assertTrue(!isRedirected); + } + + @Test + public void testIsRedirectedFailureByStatusCodeBadRequest() throws IOException { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_BAD_REQUEST) + .message( "Bad Request") + .addHeader("location", testmeurl) + .request(httpget) + .build(); + boolean isRedirected = redirectHandler.isRedirected(httpget, response, 0, new RedirectOptions()); + assertTrue(!isRedirected); + } + + @Test + public void testIsRedirectedSuccessWithStatusCodeMovedTemporarily() throws IOException { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_MOVED_TEMP) + .message("Moved Temporarily") + .addHeader("location", testmeurl) + .request(httpget) + .build(); + boolean isRedirected = redirectHandler.isRedirected(httpget, response, 0, new RedirectOptions()); + assertTrue(isRedirected); + } + + @Test + public void testIsRedirectedSuccessWithStatusCodeMovedPermanently() throws IOException { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_MOVED_PERM) + .message("Moved Permanently") + .addHeader("location", testmeurl) + .request(httpget) + .build(); + boolean isRedirected = redirectHandler.isRedirected(httpget, response, 0, new RedirectOptions()); + assertTrue(isRedirected); + } + + @Test + public void testIsRedirectedSuccessWithStatusCodeTemporaryRedirect() throws IOException { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(StatusLine.HTTP_TEMP_REDIRECT) + .message("Temporary Redirect") + .addHeader("location", testmeurl) + .request(httpget) + .build(); + boolean isRedirected = redirectHandler.isRedirected(httpget, response,0,new RedirectOptions()); + assertTrue(isRedirected); + } + + @Test + public void testIsRedirectedSuccessWithStatusCodeSeeOther() throws IOException { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_SEE_OTHER) + .message( "See Other") + .addHeader("location", testmeurl) + .request(httpget) + .build(); + boolean isRedirected = redirectHandler.isRedirected(httpget, response,0,new RedirectOptions()); + assertTrue(isRedirected); + } + + @Test + public void testGetRedirectForGetMethod() { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httpget = new Request.Builder().url(testurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_MOVED_TEMP) + .message("Moved Temporarily") + .addHeader("location", testmeurl) + .request(httpget) + .build(); + try { + Request request = redirectHandler.getRedirect(httpget, response); + assertTrue(request != null); + final String method = request.method(); + assertTrue(method.equalsIgnoreCase("GET")); + } catch (ProtocolException e) { + e.printStackTrace(); + fail("Redirect handler testGetRedirectForGetMethod failure"); + } + } + + @Test + public void testGetRedirectForGetMethodForAuthHeader() { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httpget = new Request.Builder().url(testurl).header("Authorization", "TOKEN").build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_MOVED_TEMP) + .message("Moved Temporarily") + .addHeader("location", differenthosturl) + .request(httpget) + .build(); + + try { + Request request = redirectHandler.getRedirect(httpget, response); + assertTrue(request != null); + final String method = request.method(); + assertTrue(method.equalsIgnoreCase("GET")); + String header = request.header("Authorization"); + assertTrue(header == null); + } catch (ProtocolException e) { + e.printStackTrace(); + fail("Redirect handler testGetRedirectForGetMethodForAuthHeader failure"); + } + } + + @Test + public void testGetRedirectForHeadMethod() { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httphead = new Request.Builder().url(testurl).method("HEAD", null).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_MOVED_TEMP) + .message("Moved Temporarily") + .addHeader("location", testmeurl) + .request(httphead) + .build(); + try { + Request request = redirectHandler.getRedirect(httphead, response); + assertTrue(request != null); + final String method = request.method(); + assertTrue(method.equalsIgnoreCase("HEAD")); + } catch (ProtocolException e) { + e.printStackTrace(); + fail("Redirect handler testGetRedirectForHeadMethod failure"); + } + } + + @Test + public void testGetRedirectForPostMethod() { + RedirectHandler redirectHandler = new RedirectHandler(); + RequestBody body = RequestBody.create(MediaType.parse("application/json"),""); + Request httppost = new Request.Builder().url(testurl).post(body).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_MOVED_TEMP) + .message("Moved Temporarily") + .addHeader("location", testmeurl) + .request(httppost) + .build(); + try { + Request request = redirectHandler.getRedirect(httppost, response); + assertTrue(request != null); + final String method = request.method(); + assertTrue(method.equalsIgnoreCase("POST")); + } catch (ProtocolException e) { + e.printStackTrace(); + fail("Redirect handler testGetRedirectForPostMethod failure"); + } + } + + @Test + public void testGetRedirectForPostMethodWithStatusCodeSeeOther() { + RedirectHandler redirectHandler = new RedirectHandler(); + Request httppost = new Request.Builder().url(testurl).build(); + + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_SEE_OTHER) + .message("See Other") + .addHeader("location", testmeurl) + .request(httppost) + .build(); + + try { + Request request = redirectHandler.getRedirect(httppost, response); + assertTrue(request != null); + final String method = request.method(); + assertTrue(method.equalsIgnoreCase("GET")); + } catch (ProtocolException e) { + e.printStackTrace(); + fail("Redirect handler testGetRedirectForPostMethod1 failure"); + } + } + +} diff --git a/src/test/java/com/microsoft/graph/httpcore/RetryHandlerTest.java b/src/test/java/com/microsoft/graph/httpcore/RetryHandlerTest.java new file mode 100644 index 000000000..1411eb94a --- /dev/null +++ b/src/test/java/com/microsoft/graph/httpcore/RetryHandlerTest.java @@ -0,0 +1,125 @@ +package com.microsoft.graph.httpcore; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.net.HttpURLConnection; + +import org.junit.Test; + +import com.microsoft.graph.httpcore.middlewareoption.IShouldRetry; +import com.microsoft.graph.httpcore.middlewareoption.RetryOptions; + +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class RetryHandlerTest { + + int maxRetries = 2; + int retryInterval = 1000; + String testmeurl = "https://graph.microsoft.com/v1.0/me"; + private final int HTTP_SERVER_ERROR = 500; + + @Test + public void testRetryHandlerCreation() { + RetryHandler retryhandler = new RetryHandler(); + assertNotNull(retryhandler); + } + + @Test + public void testRetryHandlerWithRetryOptions() { + RetryOptions option = new RetryOptions(); + RetryHandler retryhandler = new RetryHandler(option); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT) + .message("Gateway Timeout") + .request(httpget) + .build(); + assertTrue(retryhandler.retryRequest(response, 1, httpget, option)); + } + + @Test + public void testRetryHandlerWithCustomRetryOptions() { + IShouldRetry shouldRetry = new IShouldRetry() { + public boolean shouldRetry(long delay, int executionCount, Request request,Response response){ + return false; + } + }; + RetryOptions option = new RetryOptions(shouldRetry, 5, 0); + RetryHandler retryhandler = new RetryHandler(option); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT) + .message("Gateway Timeout") + .request(httpget) + .build(); + assertTrue(!retryhandler.retryRequest(response, 0, httpget, option)); + } + + @Test + public void testRetryRequestWithMaxRetryAttempts() { + RetryHandler retryhandler = new RetryHandler(); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT) + .message("Gateway Timeout") + .request(httpget) + .build(); + // Default retry options with Number of maxretries default to 3 + RetryOptions retryOptions = new RetryOptions(); + // Try to execute one more than allowed default max retries + int executionCount = RetryOptions.DEFAULT_MAX_RETRIES + 1; + assertFalse(retryhandler.retryRequest(response, executionCount, httpget, retryOptions)); + } + + @Test + public void testRetryRequestForStatusCode() { + RetryHandler retryhandler = new RetryHandler(); + Request httpget = new Request.Builder().url(testmeurl).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + // For status code 500 which is not in (429 503 504), So NO retry + .code(HTTP_SERVER_ERROR) + .message( "Internal Server Error") + .request(httpget) + .build(); + assertFalse(retryhandler.retryRequest(response, 1, httpget, new RetryOptions())); + } + + @Test + public void testRetryRequestWithTransferEncoding() { + RetryHandler retryhandler = new RetryHandler(); + Request httppost = new Request.Builder().url(testmeurl).post(RequestBody.create(MediaType.parse("application/json"), "TEST")).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT) + .message( "gateway timeout") + .request(httppost) + .addHeader("Transfer-Encoding", "chunked") + .build(); + assertTrue(retryhandler.retryRequest(response, 1, httppost, new RetryOptions())); + } + + @Test + public void testRetryRequestWithExponentialBackOff() { + RetryHandler retryhandler = new RetryHandler(); + Request httppost = new Request.Builder().url(testmeurl).post(RequestBody.create(MediaType.parse("application/json"), "TEST")).build(); + Response response = new Response.Builder() + .protocol(Protocol.HTTP_1_1) + .code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT) + .message( "gateway timeout") + .request(httppost) + .addHeader("Transfer-Encoding", "chunked") + .build(); + + assertTrue(retryhandler.retryRequest(response, 1, httppost, new RetryOptions())); + } +}