diff --git a/v5/java/.gitignore b/v5/java/.gitignore new file mode 100644 index 0000000..50b6768 --- /dev/null +++ b/v5/java/.gitignore @@ -0,0 +1,39 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +set_env_variables.sh \ No newline at end of file diff --git a/v5/java/build.gradle b/v5/java/build.gradle new file mode 100644 index 0000000..1960ac4 --- /dev/null +++ b/v5/java/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'org.springframework.boot' version '2.6.5' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'java' +} + +group = 'api.example' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '1.8' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-freemarker' + implementation 'org.springframework.boot:spring-boot-devtools' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/v5/java/gradle/wrapper/gradle-wrapper.jar b/v5/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..41d9927 Binary files /dev/null and b/v5/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/v5/java/gradle/wrapper/gradle-wrapper.properties b/v5/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..00e33ed --- /dev/null +++ b/v5/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/v5/java/gradlew b/v5/java/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/v5/java/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${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='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/v5/java/gradlew.bat b/v5/java/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/v5/java/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@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 execute + +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 execute + +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 + +: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 %* + +: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/v5/java/settings.gradle b/v5/java/settings.gradle new file mode 100644 index 0000000..92c378c --- /dev/null +++ b/v5/java/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java' diff --git a/v5/java/src/main/java/api/example/java/App.java b/v5/java/src/main/java/api/example/java/App.java new file mode 100644 index 0000000..0841293 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/App.java @@ -0,0 +1,13 @@ +package api.example.java; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App { + + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } + +} diff --git a/v5/java/src/main/java/api/example/java/Config.java b/v5/java/src/main/java/api/example/java/Config.java new file mode 100644 index 0000000..0c574a0 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/Config.java @@ -0,0 +1,106 @@ +package api.example.java; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.Base64Utils; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponentsBuilder; + +@Configuration +public class Config { + @Value("${climate.login.server}") + public String loginServer; + + @Value("${climate.login.path}") + public String loginPath; + + @Value("${climate.token.server}") + public String tokenServer; + + @Value("${climate.token.path}") + public String tokenPath; + + @Value("${climate.api.server}") + public String apiServer; + + @Value("${CLIENT_ID}") + public String clientId; + + @Value("${CLIENT_SECRET}") + public String clientSecret; + + @Value("${API_KEY}") + public String apiKey; + + @Value("${API_SCOPES}") + public String scopes; + + public Config() { + if (!StringUtils.hasLength(System.getenv("CLIENT_ID"))) { + throw new IllegalArgumentException("Please set environment variable CLIENT_ID"); + } + if (!StringUtils.hasLength(System.getenv("CLIENT_SECRET"))) { + throw new IllegalArgumentException("Please set environment variable CLIENT_SECRET"); + } + if (!StringUtils.hasLength(System.getenv("API_KEY"))) { + throw new IllegalArgumentException("Please set environment variable API_KEY"); + } + if (!StringUtils.hasLength(System.getenv("API_SCOPES"))) { + throw new IllegalArgumentException("Please set environment variable API_SCOPES"); + } + } + + public String buildOauthLink(String redirectUri) { + /* + * https://climate.com/static/app-login/index.html?scope=${scope} + * &page=oidcauthn&response_type=code&redirect_uri=${redirect_uri} + * &client_id=${client_id} + */ + return UriComponentsBuilder + .newInstance() + .scheme("https") + .host(loginServer) + .path(loginPath) + .queryParam("page", "oidcauthn") + .queryParam("response_type", "code") + .queryParam("scope", scopes) + .queryParam("client_id", clientId) + .queryParam("redirect_uri", redirectUri) + .build() + .toUriString(); + } + + public String buildTokenUri() { + // https://api.climate.com/api/oauth/token + return getUriComponentsBuilder(tokenServer, tokenPath); + } + + public String getBase64Credentials() { + return "Basic " + new String(Base64Utils.encode((clientId + ":" + clientSecret).getBytes())); + } + + public String buildGrowingSeasonsApiUri() { + return getUriComponentsBuilder(apiServer, "/v5/growingSeasons"); + } + + public String buildGrowingSeasonsContentsIdApiUri(String id) { + return getUriComponentsBuilder(apiServer, String.format("/v5/growingSeasonsContents/%s", id)); + } + + public String buildHarvestReportsApiUri() { + return getUriComponentsBuilder(apiServer, "/v5/harvestReports"); + } + + public String buildHarvestReportsContentsIdApiUri(String id) { + return getUriComponentsBuilder(apiServer, String.format("/v5/harvestReportsContents/%s", id)); + } + + private String getUriComponentsBuilder(String host, String path) { + return UriComponentsBuilder.newInstance() + .scheme("https") + .host(host) + .path(path) + .build() + .toString(); + } +} \ No newline at end of file diff --git a/v5/java/src/main/java/api/example/java/api/ClimateAPIs.java b/v5/java/src/main/java/api/example/java/api/ClimateAPIs.java new file mode 100644 index 0000000..b5224f2 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/api/ClimateAPIs.java @@ -0,0 +1,65 @@ +package api.example.java.api; + +import api.example.java.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import api.example.java.Config; +import org.springframework.web.reactive.function.BodyInserters; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class ClimateAPIs { + private static Logger logger = LoggerFactory.getLogger(ClimateAPIs.class); + @Autowired + protected RequestClient requestClient; + @Autowired + protected Config config; + + public GrowingSeasons createGrowingSeasons(String uri, String fieldId, String accessToken) { + Map body = new HashMap(); + body.put("fieldId", fieldId); + + GrowingSeasons growingSeasons = requestClient.getWebClientJSON(uri, accessToken, config.apiKey) + .post() + .body(BodyInserters.fromValue(body)) + .retrieve() + .bodyToMono(GrowingSeasons.class) + .block(); + + return growingSeasons; + } + + public GrowingSeasonsContentsResponse getGrowingSeasonsContents(String uri, String accessToken) { + GrowingSeasonsContentsResponse growingSeasonsContentsResponse = requestClient.getWebClient( + uri, accessToken, config.apiKey) + .get() + .retrieve() + .bodyToMono(GrowingSeasonsContentsResponse.class) + .block(); + return growingSeasonsContentsResponse; + } + + public HarvestReports createHarvestReport(String uri, HarvestReportsRequest input, String accessToken) { + HarvestReports harvestReports = requestClient.getWebClientJSON(uri, accessToken, config.apiKey) + .post() + .body(BodyInserters.fromValue(input)) + .retrieve() + .bodyToMono(HarvestReports.class) + .block(); + return harvestReports; + } + + public HarvestReportsContentsResponse getHarvestReportsContents(String uri, String accessToken) { + HarvestReportsContentsResponse harvestReportsContentsResponse = requestClient.getWebClient( + uri, accessToken, config.apiKey) + .get() + .retrieve() + .bodyToMono(HarvestReportsContentsResponse.class) + .block(); + return harvestReportsContentsResponse; + } +} diff --git a/v5/java/src/main/java/api/example/java/api/ClimateOAuth.java b/v5/java/src/main/java/api/example/java/api/ClimateOAuth.java new file mode 100644 index 0000000..54a1101 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/api/ClimateOAuth.java @@ -0,0 +1,58 @@ +package api.example.java.api; + +import api.example.java.Config; +import api.example.java.model.TokenResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; + +@Component +public class ClimateOAuth { + + @Autowired + private Config config; + + @Autowired + private RequestClient requestClient; + + private static Logger logger = LoggerFactory.getLogger(ClimateOAuth.class); + + public TokenResponse getToken(String code, String redirectUri) { + + // grant_type=authorization_code&redirect_uri=${redirect_uri}&code=${code} + MultiValueMap formData = new LinkedMultiValueMap(); + formData.add("code", code); + formData.add("grant_type", "authorization_code"); + formData.add("redirect_uri", redirectUri); + + logger.info("Request body value map: {}", formData.toString()); + + return makeRequest(formData); + } + + private TokenResponse makeRequest(MultiValueMap formData) { + TokenResponse tokenResponse = requestClient.getWebClient(config.buildTokenUri(), config.getBase64Credentials()) + .post() + .body(BodyInserters.fromFormData(formData)) + .retrieve().bodyToMono(TokenResponse.class) + .block(); + return tokenResponse; + } + + public TokenResponse getRefreshToken(String refreshToken) { + + // grant_type=authorization_code&redirect_uri=${redirect_uri}&code=${code} + MultiValueMap formData = new LinkedMultiValueMap(); + formData.add("refresh_token", refreshToken); + formData.add("grant_type", "refresh_token"); + + logger.info("Request body value map: {}", formData.toString()); + + return makeRequest(formData); + } + +} diff --git a/v5/java/src/main/java/api/example/java/api/RequestClient.java b/v5/java/src/main/java/api/example/java/api/RequestClient.java new file mode 100644 index 0000000..dcad03b --- /dev/null +++ b/v5/java/src/main/java/api/example/java/api/RequestClient.java @@ -0,0 +1,59 @@ +package api.example.java.api; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.codec.LoggingCodecSupport; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class RequestClient { + + public WebClient getWebClient(String uri, String accessToken, String apiKey) { + return WebClient.builder() + .baseUrl(uri).exchangeStrategies(exchangeStrategies()) + .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .defaultHeader(HttpHeaders.ACCEPT, MediaType.ALL_VALUE) + .defaultHeader("x-api-key", apiKey) + .build(); + } + + public WebClient getWebClient(String uri, String auth) { + return WebClient.builder() + .baseUrl(uri) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .defaultHeader(HttpHeaders.AUTHORIZATION, auth) + .exchangeStrategies(exchangeStrategies()) + .build(); + } + + public WebClient getWebClientJSON(String uri, String accessToken, String apiKey) { + return WebClient.builder() + .baseUrl(uri).exchangeStrategies(exchangeStrategies()) + .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .defaultHeader(HttpHeaders.ACCEPT, MediaType.ALL_VALUE) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .defaultHeader("x-api-key", apiKey) + .build(); + } + + public WebClient getWebClientId(String uri, String id, String accessToken, String apiKey) { + return WebClient.builder() + .baseUrl(uri).exchangeStrategies(exchangeStrategies()) + .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .defaultHeader(HttpHeaders.ACCEPT, MediaType.ALL_VALUE) + .defaultHeader("x-api-key", apiKey) + .build(); + } + + private ExchangeStrategies exchangeStrategies() { + ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults(); + exchangeStrategies + .messageWriters() + .stream() + .filter(LoggingCodecSupport.class::isInstance) + .forEach(writer -> ((LoggingCodecSupport) writer).setEnableLoggingRequestDetails(true)); + return exchangeStrategies; + } +} diff --git a/v5/java/src/main/java/api/example/java/controllers/BaseController.java b/v5/java/src/main/java/api/example/java/controllers/BaseController.java new file mode 100644 index 0000000..78d9683 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/controllers/BaseController.java @@ -0,0 +1,65 @@ +package api.example.java.controllers; + +import api.example.java.Config; +import api.example.java.model.TokenResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +public class BaseController { + protected static final String LOGIN_REDIRECT = "login-redirect"; + protected static final String HOME_PAGE = "home"; + protected static final String INDEX_PAGE = "index"; + protected static final String REDIRECT_TO_HOME_PAGE = "redirect:/"; + protected static final String TOKEN_RESPONSE = "tokenResponse"; + protected static final String CODE = "code"; + protected static final String REFRESH_TOKEN = "refresh_token"; + @Autowired + protected Config config; + + protected String getAccessTokenFromSession(HttpServletRequest request) { + String token = ""; + if (request.getSession().getAttribute(TOKEN_RESPONSE) instanceof TokenResponse) { + TokenResponse tokenResponse = (TokenResponse) request.getSession().getAttribute(TOKEN_RESPONSE); + token = tokenResponse.getAccessToken(); + } + return token; + } + + protected boolean isUserLoggedIn(HttpSession session) { + return session.getAttribute(TOKEN_RESPONSE) != null; + } + + @ExceptionHandler(WebClientResponseException.class) + public ResponseEntity handleWebClientResponseException(WebClientResponseException ex) { + return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString()); + } + + protected void saveTokenResponseInSession(HttpServletRequest request, TokenResponse tokenResponse) { + request.getSession().setAttribute(TOKEN_RESPONSE, tokenResponse); + } + + protected void cleanSession(HttpServletRequest request) { + request.getSession().removeAttribute(TOKEN_RESPONSE); + } + + protected String growingSeasonsApiUri() { + return config.buildGrowingSeasonsApiUri(); + } + + protected String growingSeasonsContentsApiUri(String id) { + return config.buildGrowingSeasonsContentsIdApiUri(id); + } + + protected String harvestReportsApiUri() { + return config.buildHarvestReportsApiUri(); + } + + protected String harvestReportsContentsApiUri(String id) { + return config.buildHarvestReportsContentsIdApiUri(id); + } +} diff --git a/v5/java/src/main/java/api/example/java/controllers/GrowingSeasonsController.java b/v5/java/src/main/java/api/example/java/controllers/GrowingSeasonsController.java new file mode 100644 index 0000000..da87403 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/controllers/GrowingSeasonsController.java @@ -0,0 +1,54 @@ +package api.example.java.controllers; + +import javax.servlet.http.HttpServletRequest; + +import api.example.java.model.GrowingSeasonsRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; + +import api.example.java.api.ClimateAPIs; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + + +@Controller +public class GrowingSeasonsController extends BaseController { + + private static final String GROWINGSEASONS = "growingSeasons"; + + private static final String GROWINGSEASONS_PAGE = GROWINGSEASONS; + + private static final String GROWINGSEASONSCONTENTS = "growingSeasonsContents"; + + private static final String GROWINGSEASONSCONTENTS_PAGE = GROWINGSEASONSCONTENTS; + @Autowired + private ClimateAPIs climateAPIs; + private static Logger logger = LoggerFactory.getLogger(GrowingSeasonsController.class); + + @GetMapping("/growingSeasons") + public String createGrowingSeasons() { + logger.info("Get createGrowingSeasons entered"); + return GROWINGSEASONS_PAGE; + } + + @PostMapping(value="/growingSeasons", consumes=MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public String createGrowingSeasons(Model model, HttpServletRequest request, GrowingSeasonsRequest fieldId) { + logger.info("Post createGrowingSeasons entered"); + logger.info("FieldId entered is " + fieldId.getFieldId()); + model.addAttribute(GROWINGSEASONS, climateAPIs.createGrowingSeasons(growingSeasonsApiUri(), fieldId.getFieldId(), getAccessTokenFromSession(request))); + return GROWINGSEASONS_PAGE; + } + + @GetMapping("/growingSeasonsContents/{id}") + public String getGrowingSeasonsContents(Model model, HttpServletRequest request, @PathVariable String id) { + logger.info("Get createGrowingSeasonsContents entered"); + logger.info("growingSeasonsContentsId entered is " + id); + model.addAttribute(GROWINGSEASONSCONTENTS, climateAPIs.getGrowingSeasonsContents(growingSeasonsContentsApiUri(id), getAccessTokenFromSession(request))); + return GROWINGSEASONSCONTENTS_PAGE; + } +} diff --git a/v5/java/src/main/java/api/example/java/controllers/HarvestReportsController.java b/v5/java/src/main/java/api/example/java/controllers/HarvestReportsController.java new file mode 100644 index 0000000..b3f2c13 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/controllers/HarvestReportsController.java @@ -0,0 +1,56 @@ +package api.example.java.controllers; + +import api.example.java.api.ClimateAPIs; +import api.example.java.model.HarvestReportsRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +import javax.servlet.http.HttpServletRequest; + +@Controller +public class HarvestReportsController extends BaseController { + private static final String HARVESTREPORTS = "harvestReports"; + + private static final String HARVESTREPORTS_PAGE = HARVESTREPORTS; + + private static final String HARVESTREPORTSCONTENTS = "harvestReportsContents"; + + private static final String HARVESTREPORTSCONTENTS_PAGE = HARVESTREPORTSCONTENTS; + + @Autowired + private ClimateAPIs climateAPIs; + private static Logger logger = LoggerFactory.getLogger(GrowingSeasonsController.class); + + @GetMapping("/harvestReports") + public String createHarvestReports() { + logger.info("Get createHarvestReports entered"); + return HARVESTREPORTS_PAGE; + } + + @PostMapping(value="/harvestReports", consumes=MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public String createHarvestReports(Model model, HttpServletRequest request, HarvestReportsRequest input) { + logger.info("Post createHarvestReports entered"); + logger.info("FieldId entered is " + input.getFieldId()); + logger.info("GrowingSeasons entered is " + input.getGrowingSeasons()); + model.addAttribute(HARVESTREPORTS, climateAPIs.createHarvestReport( + harvestReportsApiUri(), input, getAccessTokenFromSession(request))); + return HARVESTREPORTS_PAGE; + } + + @GetMapping("/harvestReportsContents/{id}") + public String getHarvestReportsContents(Model model, HttpServletRequest request, @PathVariable String id) { + logger.info("getHarvestReportsContents entered"); + logger.info("harvestReportsContentsId entered is " + id); + model.addAttribute(HARVESTREPORTSCONTENTS, climateAPIs.getHarvestReportsContents( + harvestReportsContentsApiUri(id), getAccessTokenFromSession(request))); + return HARVESTREPORTSCONTENTS_PAGE; + } + +} diff --git a/v5/java/src/main/java/api/example/java/controllers/HomeController.java b/v5/java/src/main/java/api/example/java/controllers/HomeController.java new file mode 100644 index 0000000..64f9cb9 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/controllers/HomeController.java @@ -0,0 +1,28 @@ +package api.example.java.controllers; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +@Controller +public class HomeController extends BaseController { + private static final String LOGIN_URI = "loginUri"; + + @GetMapping("/") + public String home(Model model, HttpSession session, HttpServletRequest request) { + if (isUserLoggedIn(session)) { + return HOME_PAGE; + } + model.addAttribute(LOGIN_URI, config.buildOauthLink( + ServletUriComponentsBuilder + .fromRequest(request) + .pathSegment(LOGIN_REDIRECT) + .build() + .toString())); + return INDEX_PAGE; + } +} diff --git a/v5/java/src/main/java/api/example/java/controllers/LoginController.java b/v5/java/src/main/java/api/example/java/controllers/LoginController.java new file mode 100644 index 0000000..7295793 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/controllers/LoginController.java @@ -0,0 +1,44 @@ +package api.example.java.controllers; + +import api.example.java.api.ClimateOAuth; +import api.example.java.model.TokenResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import javax.servlet.http.HttpServletRequest; + +@Controller +public class LoginController extends BaseController { + + @Autowired + private ClimateOAuth oAuth; + + @GetMapping("/login-redirect") + public String loginRedirect(Model model, @RequestParam(CODE) String code, HttpServletRequest request) { + model.addAttribute(CODE, code); + TokenResponse tokenResponse = oAuth.getToken(code, request.getRequestURL().toString()); + saveTokenResponseInSession(request, tokenResponse); + model.addAttribute(TOKEN_RESPONSE, tokenResponse); + return HOME_PAGE; + } + + @GetMapping("/refresh-token") + public String refreshToken(Model model, @RequestParam(REFRESH_TOKEN) String refreshToken, + HttpServletRequest request) { + + TokenResponse tokenResponse = oAuth.getRefreshToken(refreshToken); + saveTokenResponseInSession(request, tokenResponse); + model.addAttribute(TOKEN_RESPONSE, tokenResponse); + return HOME_PAGE; + } + + @GetMapping("/logout") + public String logout(Model model, HttpServletRequest request) { + cleanSession(request); + return REDIRECT_TO_HOME_PAGE; + } + +} diff --git a/v5/java/src/main/java/api/example/java/model/GrowingSeasons.java b/v5/java/src/main/java/api/example/java/model/GrowingSeasons.java new file mode 100644 index 0000000..28b970b --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/GrowingSeasons.java @@ -0,0 +1,16 @@ +package api.example.java.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class GrowingSeasons { + @JsonProperty("contentId") + private String contentId; + + public String getContentId() { + return contentId; + } + + public void setContentId(String contentId) { + this.contentId = contentId; + } +} diff --git a/v5/java/src/main/java/api/example/java/model/GrowingSeasonsContents.java b/v5/java/src/main/java/api/example/java/model/GrowingSeasonsContents.java new file mode 100644 index 0000000..0cb7e8a --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/GrowingSeasonsContents.java @@ -0,0 +1,22 @@ +package api.example.java.model; + +public class GrowingSeasonsContents { + private String id; + private String year; + + public String getId() { + return id; + } + + public String getYear() { + return year; + } + + public void setId(String id) { + this.id = id; + } + + public void setYear(String year) { + this.year = year; + } +} diff --git a/v5/java/src/main/java/api/example/java/model/GrowingSeasonsContentsResponse.java b/v5/java/src/main/java/api/example/java/model/GrowingSeasonsContentsResponse.java new file mode 100644 index 0000000..70ed794 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/GrowingSeasonsContentsResponse.java @@ -0,0 +1,18 @@ +package api.example.java.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class GrowingSeasonsContentsResponse { + @JsonProperty("results") + private List results; + + public List getResults() { + return results; + } + + public void setResults(List results) { + this.results = results; + } +} diff --git a/v5/java/src/main/java/api/example/java/model/GrowingSeasonsRequest.java b/v5/java/src/main/java/api/example/java/model/GrowingSeasonsRequest.java new file mode 100644 index 0000000..65e673b --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/GrowingSeasonsRequest.java @@ -0,0 +1,13 @@ +package api.example.java.model; + +public class GrowingSeasonsRequest { + private String fieldId; + + public String getFieldId() { + return fieldId; + } + + public void setFieldId(String fieldId) { + this.fieldId = fieldId; + } +} diff --git a/v5/java/src/main/java/api/example/java/model/HarvestReports.java b/v5/java/src/main/java/api/example/java/model/HarvestReports.java new file mode 100644 index 0000000..51c3cae --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/HarvestReports.java @@ -0,0 +1,16 @@ +package api.example.java.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class HarvestReports { + @JsonProperty("id") + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/v5/java/src/main/java/api/example/java/model/HarvestReportsContents.java b/v5/java/src/main/java/api/example/java/model/HarvestReportsContents.java new file mode 100644 index 0000000..fc558db --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/HarvestReportsContents.java @@ -0,0 +1,193 @@ +package api.example.java.model; + +public class HarvestReportsContents { + private String locationId; + private String growingSeason; + private String cropId; + private Boolean phaPresent; + private String latestPhaDate; + private String initialDataReceivedOn; + private String latestDataReceivedOn; + private String latestHarvestDate; + private Integer distinctSeedProducts; + private Double totalMoistureMass; + private Double totalMoistureMassPhaAdjusted; + private Double totalWetMass; + private Double totalWetMassPhaAdjusted; + private Double totalHarvestedArea; + private Double totalHarvestedAreaPhaAdjusted; + private Double climateCalculatedAverageYield; + private Double climateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings; + private Double climateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings; + private Double growerAdjustedNominalWeight; + private Double growerAdjustedNominalMoisture; + private Double growerAdjustedShrinkFactor; + + public String getLocationId() { + return locationId; + } + + public void setLocationId(String locationId) { + this.locationId = locationId; + } + + public String getGrowingSeason() { + return growingSeason; + } + + public void setGrowingSeason(String growingSeason) { + this.growingSeason = growingSeason; + } + + public String getCropId() { + return cropId; + } + + public void setCropId(String cropId) { + this.cropId = cropId; + } + + public Boolean getPhaPresent() { + return phaPresent; + } + + public void setPhaPresent(Boolean phaPresent) { + this.phaPresent = phaPresent; + } + + public String getLatestPhaDate() { + return latestPhaDate; + } + + public void setLatestPhaDate(String latestPhaDate) { + this.latestPhaDate = latestPhaDate; + } + + public String getInitialDataReceivedOn() { + return initialDataReceivedOn; + } + + public void setInitialDataReceivedOn(String initialDataReceivedOn) { + this.initialDataReceivedOn = initialDataReceivedOn; + } + + public String getLatestDataReceivedOn() { + return latestDataReceivedOn; + } + + public void setLatestDataReceivedOn(String latestDataReceivedOn) { + this.latestDataReceivedOn = latestDataReceivedOn; + } + + public String getLatestHarvestDate() { + return latestHarvestDate; + } + + public void setLatestHarvestDate(String latestHarvestDate) { + this.latestHarvestDate = latestHarvestDate; + } + + public Integer getDistinctSeedProducts() { + return distinctSeedProducts; + } + + public void setDistinctSeedProducts(Integer distinctSeedProducts) { + this.distinctSeedProducts = distinctSeedProducts; + } + + public Double getTotalMoistureMass() { + return totalMoistureMass; + } + + public void setTotalMoistureMass(Double totalMoistureMass) { + this.totalMoistureMass = totalMoistureMass; + } + + public Double getTotalMoistureMassPhaAdjusted() { + return totalMoistureMassPhaAdjusted; + } + + public void setTotalMoistureMassPhaAdjusted(Double totalMoistureMassPhaAdjusted) { + this.totalMoistureMassPhaAdjusted = totalMoistureMassPhaAdjusted; + } + + public Double getTotalWetMass() { + return totalWetMass; + } + + public void setTotalWetMass(Double totalWetMass) { + this.totalWetMass = totalWetMass; + } + + public Double getTotalWetMassPhaAdjusted() { + return totalWetMassPhaAdjusted; + } + + public void setTotalWetMassPhaAdjusted(Double totalWetMassPhaAdjusted) { + this.totalWetMassPhaAdjusted = totalWetMassPhaAdjusted; + } + + public Double getTotalHarvestedArea() { + return totalHarvestedArea; + } + + public void setTotalHarvestedArea(Double totalHarvestedArea) { + this.totalHarvestedArea = totalHarvestedArea; + } + + public Double getTotalHarvestedAreaPhaAdjusted() { + return totalHarvestedAreaPhaAdjusted; + } + + public void setTotalHarvestedAreaPhaAdjusted(Double totalHarvestedAreaPhaAdjusted) { + this.totalHarvestedAreaPhaAdjusted = totalHarvestedAreaPhaAdjusted; + } + + public Double getClimateCalculatedAverageYield() { + return climateCalculatedAverageYield; + } + + public void setClimateCalculatedAverageYield(Double climateCalculatedAverageYield) { + this.climateCalculatedAverageYield = climateCalculatedAverageYield; + } + + public Double getClimateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings() { + return climateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings; + } + + public void setClimateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings(Double climateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings) { + this.climateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings = climateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings; + } + + public Double getClimateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings() { + return climateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings; + } + + public void setClimateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings(Double climateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings) { + this.climateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings = climateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings; + } + + public Double getGrowerAdjustedNominalWeight() { + return growerAdjustedNominalWeight; + } + + public void setGrowerAdjustedNominalWeight(Double growerAdjustedNominalWeight) { + this.growerAdjustedNominalWeight = growerAdjustedNominalWeight; + } + + public Double getGrowerAdjustedNominalMoisture() { + return growerAdjustedNominalMoisture; + } + + public void setGrowerAdjustedNominalMoisture(Double growerAdjustedNominalMoisture) { + this.growerAdjustedNominalMoisture = growerAdjustedNominalMoisture; + } + + public Double getGrowerAdjustedShrinkFactor() { + return growerAdjustedShrinkFactor; + } + + public void setGrowerAdjustedShrinkFactor(Double growerAdjustedShrinkFactor) { + this.growerAdjustedShrinkFactor = growerAdjustedShrinkFactor; + } +} diff --git a/v5/java/src/main/java/api/example/java/model/HarvestReportsContentsResponse.java b/v5/java/src/main/java/api/example/java/model/HarvestReportsContentsResponse.java new file mode 100644 index 0000000..152ca59 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/HarvestReportsContentsResponse.java @@ -0,0 +1,18 @@ +package api.example.java.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class HarvestReportsContentsResponse { + @JsonProperty("results") + private List results; + + public List getResults() { + return results; + } + + public void setResults(List results) { + this.results = results; + } +} diff --git a/v5/java/src/main/java/api/example/java/model/HarvestReportsRequest.java b/v5/java/src/main/java/api/example/java/model/HarvestReportsRequest.java new file mode 100644 index 0000000..b8b6850 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/HarvestReportsRequest.java @@ -0,0 +1,27 @@ +package api.example.java.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class HarvestReportsRequest { + @JsonProperty("fieldId") + private String fieldId; + + @JsonProperty("growingSeasons") + private String[] growingSeasons; + + public String getFieldId() { + return fieldId; + } + + public String[] getGrowingSeasons() { + return growingSeasons; + } + + public void setFieldId(String fieldId) { + this.fieldId = fieldId; + } + + public void setGrowingSeasons(String[] growingSeasons) { + this.growingSeasons = growingSeasons; + } +} diff --git a/v5/java/src/main/java/api/example/java/model/TokenResponse.java b/v5/java/src/main/java/api/example/java/model/TokenResponse.java new file mode 100644 index 0000000..569975c --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/TokenResponse.java @@ -0,0 +1,50 @@ +package api.example.java.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TokenResponse { + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("refresh_token") + private String responseToken; + + @JsonProperty("scope") + private String scopes; + + private User user; + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return responseToken; + } + + public void setResponseToken(String responseToken) { + this.responseToken = responseToken; + } + + public String getScopes() { + return scopes; + } + + public void setScopes(String scopes) { + this.scopes = scopes; + } + +} diff --git a/v5/java/src/main/java/api/example/java/model/User.java b/v5/java/src/main/java/api/example/java/model/User.java new file mode 100644 index 0000000..c2245b4 --- /dev/null +++ b/v5/java/src/main/java/api/example/java/model/User.java @@ -0,0 +1,42 @@ +package api.example.java.model; + +public class User { + + private String email; + private String firstName; + private String lastName; + private String country; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFirstname() { + return firstName; + } + + public void setFirstname(String firstName) { + this.firstName = firstName; + } + + public String getLastname() { + return lastName; + } + + public void setLastname(String lastName) { + this.lastName = lastName; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + +} diff --git a/v5/java/src/main/resources/application.properties b/v5/java/src/main/resources/application.properties new file mode 100644 index 0000000..b9d2799 --- /dev/null +++ b/v5/java/src/main/resources/application.properties @@ -0,0 +1,12 @@ +spring.freemarker.template-loader-path: classpath:/templates +spring.freemarker.suffix: .ftl +spring.devtools.restart.additional-paths = src/main/resources/templates +spring.freemarker.cache = false +climate.login.server = climate.com +climate.login.path = /static/app-login/index.html +climate.token.server = api.climate.com +climate.token.path = /api/oauth/token +climate.api.server = platform.climate.com +logging.level.org.springframework.web.reactive.function.client.ExchangeFunctions=TRACE +spring.http.log-request-details=true +spring.output.ansi.enabled=ALWAYS \ No newline at end of file diff --git a/v5/java/src/main/resources/static/favicon.png b/v5/java/src/main/resources/static/favicon.png new file mode 100644 index 0000000..bac278b Binary files /dev/null and b/v5/java/src/main/resources/static/favicon.png differ diff --git a/v5/java/src/main/resources/static/fv-login-button.png b/v5/java/src/main/resources/static/fv-login-button.png new file mode 100644 index 0000000..d20b7f8 Binary files /dev/null and b/v5/java/src/main/resources/static/fv-login-button.png differ diff --git a/v5/java/src/main/resources/templates/footer.ftl b/v5/java/src/main/resources/templates/footer.ftl new file mode 100644 index 0000000..fd2eceb --- /dev/null +++ b/v5/java/src/main/resources/templates/footer.ftl @@ -0,0 +1,21 @@ + +<#if logout_link?has_content> + Logout + +<#if tokenResponse?has_content> + Logout +  | + Home +  | + Refresh Token + <#if tokenResponse.scopes?has_content> + <#if tokenResponse.scopes?contains("user")> +  | + Growing Seasons + + <#if tokenResponse.scopes?contains("queries:read") && tokenResponse.scopes?contains("queries:write")> +  | + Harvest Reports + + + diff --git a/v5/java/src/main/resources/templates/growingSeasons.ftl b/v5/java/src/main/resources/templates/growingSeasons.ftl new file mode 100644 index 0000000..be0983e --- /dev/null +++ b/v5/java/src/main/resources/templates/growingSeasons.ftl @@ -0,0 +1,14 @@ +<#include "standardPage.ftl" /> + +<@standardPage title="Api Java Example"> +

Partner API Demo Site

+

Welcome to the Climate Partner Demo App.

+ <#if growingSeasons?has_content> +

Growing Seasons Content Id: ${growingSeasons.contentId}

+ <#else> +
+

Field Id:

+

+
+ + \ No newline at end of file diff --git a/v5/java/src/main/resources/templates/growingSeasonsContents.ftl b/v5/java/src/main/resources/templates/growingSeasonsContents.ftl new file mode 100644 index 0000000..05e4a5c --- /dev/null +++ b/v5/java/src/main/resources/templates/growingSeasonsContents.ftl @@ -0,0 +1,22 @@ +<#include "standardPage.ftl" /> + +<@standardPage title="Api Java Example"> +

Partner API Demo Site

+

Welcome to the Climate Partner Demo App.

+ + + + + + <#list growingSeasonsContents.results as growingSeasonsContent> + + + + + +
Id Year
+ ${growingSeasonsContent.id} + + ${growingSeasonsContent.year} +
+ \ No newline at end of file diff --git a/v5/java/src/main/resources/templates/harvestReports.ftl b/v5/java/src/main/resources/templates/harvestReports.ftl new file mode 100644 index 0000000..43b06f0 --- /dev/null +++ b/v5/java/src/main/resources/templates/harvestReports.ftl @@ -0,0 +1,15 @@ +<#include "standardPage.ftl" /> + +<@standardPage title="Api Java Example"> +

Partner API Demo Site

+

Welcome to the Climate Partner Demo App.

+ <#if harvestReports?has_content> +

Harvest Reports Id: ${harvestReports.id}

+ <#else> +
+

Field Id:

+

Growing Seasons Ids: (Comma delimited)

+

+
+ + \ No newline at end of file diff --git a/v5/java/src/main/resources/templates/harvestReportsContents.ftl b/v5/java/src/main/resources/templates/harvestReportsContents.ftl new file mode 100644 index 0000000..043a7dd --- /dev/null +++ b/v5/java/src/main/resources/templates/harvestReportsContents.ftl @@ -0,0 +1,98 @@ +<#include "standardPage.ftl" /> + +<@standardPage title="Api Java Example"> +

Partner API Demo Site

+

Welcome to the Climate Partner Demo App.

+ + + + + + + + + + + + + + + + + + + + + + + + + <#list harvestReportsContents.results as harvestReportsContent> + + + + + + + + + + + + + + + + + + + + + + + + +
locationId growingSeason cropId phaPresent latestPhaDate initialDataReceivedOn latestDataReceivedOn latestHarvestDate distinctSeedProducts totalMoistureMass totalMoistureMassPhaAdjusted totalWetMass totalWetMassPhaAdjusted totalHarvestedArea totalHarvestedAreaPhaAdjusted climateCalculatedAverageYield climateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings climateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings growerAdjustedNominalWeight growerAdjustedNominalMoisture growerAdjustedShrinkFactor
+ ${harvestReportsContent.locationId} + + ${harvestReportsContent.growingSeason} + + ${harvestReportsContent.cropId} + + ${harvestReportsContent.phaPresent?string} + + ${harvestReportsContent.latestPhaDate} + + ${harvestReportsContent.initialDataReceivedOn} + + ${harvestReportsContent.latestDataReceivedOn} + + ${harvestReportsContent.latestHarvestDate} + + ${harvestReportsContent.distinctSeedProducts} + + ${harvestReportsContent.totalMoistureMass} + + ${harvestReportsContent.totalMoistureMassPhaAdjusted} + + ${harvestReportsContent.totalWetMass} + + ${harvestReportsContent.totalWetMassPhaAdjusted} + + ${harvestReportsContent.totalHarvestedArea} + + ${harvestReportsContent.totalHarvestedAreaPhaAdjusted} + + ${harvestReportsContent.climateCalculatedAverageYield} + + ${harvestReportsContent.climateCalculatedGrowerAdjustedAverageYieldDefaultCropSettings} + + ${harvestReportsContent.climateCalculatedGrowerAdjustedAverageYieldGrowerCropSettings} + + ${harvestReportsContent.growerAdjustedNominalWeight} + + ${harvestReportsContent.growerAdjustedNominalMoisture} + + ${harvestReportsContent.growerAdjustedShrinkFactor} +
+ \ No newline at end of file diff --git a/v5/java/src/main/resources/templates/home.ftl b/v5/java/src/main/resources/templates/home.ftl new file mode 100644 index 0000000..8127abd --- /dev/null +++ b/v5/java/src/main/resources/templates/home.ftl @@ -0,0 +1,18 @@ +<#include "standardPage.ftl" /> + +<@standardPage title="Api Java Example"> +

Partner API Demo Site

+

Welcome to the Climate Partner Demo App.

+

+ Welcome - ${tokenResponse.user.firstname} ${tokenResponse.user.lastname} +

+

+ Email - ${tokenResponse.user.email}
+ Country - ${tokenResponse.user.country} +

+

+ Access Token - ${tokenResponse.accessToken}
+ Refresh Token - ${tokenResponse.refreshToken}
+ Scopes - ${tokenResponse.scopes} +

+ \ No newline at end of file diff --git a/v5/java/src/main/resources/templates/index.ftl b/v5/java/src/main/resources/templates/index.ftl new file mode 100644 index 0000000..3893dd2 --- /dev/null +++ b/v5/java/src/main/resources/templates/index.ftl @@ -0,0 +1,13 @@ +<#include "standardPage.ftl" /> + +<@standardPage title="Api Java Example"> +

Partner API Demo Site

+

Welcome to the Climate Partner Demo App.

+

Imagine that this page is your great web application and you want + to connect it with Climate FieldView. To do this, you need to let your + users establish a secure connection between your app and FieldView. You + do this using Log In with FieldView.

+

+ +

+ diff --git a/v5/java/src/main/resources/templates/standardPage.ftl b/v5/java/src/main/resources/templates/standardPage.ftl new file mode 100644 index 0000000..645670b --- /dev/null +++ b/v5/java/src/main/resources/templates/standardPage.ftl @@ -0,0 +1,29 @@ +<#macro standardPage title=""> + + + + ${title} + + + + + + <#nested/> + <#include "footer.ftl"> + + + \ No newline at end of file diff --git a/v5/java/src/test/java/api/example/java/AppTests.java b/v5/java/src/test/java/api/example/java/AppTests.java new file mode 100644 index 0000000..b14018c --- /dev/null +++ b/v5/java/src/test/java/api/example/java/AppTests.java @@ -0,0 +1,13 @@ +package api.example.java; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AppTests { + + @Test + void contextLoads() { + } + +}