commit 0e8d7b5d317f9833c563001963523cfd9bf857c5 Author: github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed Nov 15 07:02:37 2023 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84b1cad --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/build +/.idea +/.gradle +/tomcat \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..2672e0f --- /dev/null +++ b/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'war' + id 'java' + id "de.undercouch.download" version "4.1.2" +} + +group 'ch.hevs.sdi' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1' + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.3' +} + +task downloadTomcat(type: Download) { + onlyIf { !file('tomcat').exists() && !file('tomcat.zip').exists() } + src "https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.56/bin/apache-tomcat-9.0.56.zip" + dest new File("tomcat.zip") +} + +task installTomcat(dependsOn: downloadTomcat, type: Copy) { + onlyIf { !file('tomcat').exists() } + from zipTree(downloadTomcat.dest) + into projectDir + fileMode 0777 + doLast { + delete("tomcat.zip") + file("apache-tomcat-9.0.56").renameTo(file("tomcat")) + } +} + +clean.doFirst { + delete 'tomcat' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..62d4c05 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..070cb70 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..fbd7c51 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## 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='"-Xmx64m" "-Xms64m"' + +# 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 or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..a9f778a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@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 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/requests.json b/requests.json new file mode 100644 index 0000000..5331fd3 --- /dev/null +++ b/requests.json @@ -0,0 +1,347 @@ +{ + "_type": "export", + "__export_format": 4, + "__export_date": "2020-11-12T13:07:01.573Z", + "__export_source": "insomnia.desktop.app:v7.1.1", + "resources": [ + { + "_id": "req_1db70f84801d4bcd9d556985463b3260", + "authentication": {}, + "body": {}, + "created": 1605183945963, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1605183945963, + "method": "GET", + "modified": 1605186284856, + "name": "/things", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things", + "_type": "request" + }, + { + "_id": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "created": 1580469239552, + "description": "Test your Servlet implementation", + "modified": 1605186255923, + "name": "Servlet exercise", + "parentId": null, + "_type": "workspace" + }, + { + "_id": "req_5d63c80479024ebda4de91aa83770e16", + "authentication": {}, + "body": {}, + "created": 1605184021523, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1605183945913, + "method": "POST", + "modified": 1605184074993, + "name": "/things?type=THING&name=thing3", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things?type=THING&name=thing3", + "_type": "request" + }, + { + "_id": "req_135c7d01110f4118b0262f0093bc9aad", + "authentication": {}, + "body": {}, + "created": 1605184126068, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1605183945863, + "method": "GET", + "modified": 1605184143461, + "name": "/things/thing3", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3", + "_type": "request" + }, + { + "_id": "req_94772e7f6aae40499286707d12f5e7d7", + "authentication": {}, + "body": {}, + "created": 1605184174226, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1605183945813, + "method": "DELETE", + "modified": 1605184186114, + "name": "/things/thing1", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing1", + "_type": "request" + }, + { + "_id": "req_e31414220bca4ec393a26217cea0f159", + "authentication": {}, + "body": {}, + "created": 1605184236335, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1605183945763, + "method": "POST", + "modified": 1605184272967, + "name": "/things/thing3?type=INPUT&name=input1", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3?type=INPUT&name=input1", + "_type": "request" + }, + { + "_id": "req_d85cb89a52bc4794a4e588e184787f93", + "authentication": {}, + "body": {}, + "created": 1605184291572, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1595431884388.5, + "method": "POST", + "modified": 1605184295768, + "name": "/things/thing3?type=INPUT&name=input2", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3?type=INPUT&name=input2", + "_type": "request" + }, + { + "_id": "req_dd512445f02c4cdbaeb859c3d1cee32e", + "authentication": {}, + "body": {}, + "created": 1605184381646, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1595431884363.5, + "method": "GET", + "modified": 1605184409285, + "name": "/things/thing3/input2", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3/input2", + "_type": "request" + }, + { + "_id": "req_8428dce2429f458fb7b5a15f5aa06af9", + "authentication": {}, + "body": {}, + "created": 1605184319605, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1595431884338.5, + "method": "DELETE", + "modified": 1605184349676, + "name": "/things/thing3/input2", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3/input2", + "_type": "request" + }, + { + "_id": "req_460e95e3836942cf9e62611c2845f2a8", + "authentication": {}, + "body": {}, + "created": 1605184443955, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1595431884288.5, + "method": "POST", + "modified": 1605184454952, + "name": "/things/thing3?type=OUTPUT&name=output1", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3?type=OUTPUT&name=output1", + "_type": "request" + }, + { + "_id": "req_83bc099adb3a4f2c900b6a5862533042", + "authentication": {}, + "body": {}, + "created": 1605184467201, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1590555853651.25, + "method": "POST", + "modified": 1605184470669, + "name": "/things/thing3?type=OUTPUT&name=output2", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3?type=OUTPUT&name=output2", + "_type": "request" + }, + { + "_id": "req_55c2759a6f154bd5896bf8965d216943", + "authentication": {}, + "body": {}, + "created": 1605185642884, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1590555853626.25, + "method": "GET", + "modified": 1605185654190, + "name": "/things/thing3/output2", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3/output2", + "_type": "request" + }, + { + "_id": "req_408264b263e64befa468b30222f6a2cb", + "authentication": {}, + "body": { + "mimeType": "application/json", + "text": "{\n\t\"value\": 17\n}" + }, + "created": 1605185678372, + "description": "", + "headers": [ + { + "id": "pair_c4fa551df1014811940a68edd68ef45f", + "name": "Content-Type", + "value": "application/json" + } + ], + "isPrivate": false, + "metaSortKey": -1590555853613.75, + "method": "PUT", + "modified": 1605186198507, + "name": "/things/thing3/output2", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3/output2", + "_type": "request" + }, + { + "_id": "req_8f64f0c0da8e48dc8e5dfddb9f676a6f", + "authentication": {}, + "body": {}, + "created": 1605184485778, + "description": "", + "headers": [], + "isPrivate": false, + "metaSortKey": -1590555853601.25, + "method": "DELETE", + "modified": 1605184495254, + "name": "/things/thing3/output2", + "parameters": [], + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "settingDisableRenderRequestBody": false, + "settingEncodeUrl": true, + "settingFollowRedirects": "global", + "settingRebuildPath": true, + "settingSendCookies": true, + "settingStoreCookies": true, + "url": "localhost:8080/things/thing3/output2", + "_type": "request" + }, + { + "_id": "env_1a3df52f6c2096100347e800dc7e4720381b6f79", + "color": null, + "created": 1580469239582, + "data": {}, + "dataPropertyOrder": null, + "isPrivate": false, + "metaSortKey": 1580469239582, + "modified": 1580469239582, + "name": "Base Environment", + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "_type": "environment" + }, + { + "_id": "jar_1a3df52f6c2096100347e800dc7e4720381b6f79", + "cookies": [], + "created": 1580469239583, + "modified": 1580469239583, + "name": "Default Jar", + "parentId": "wrk_61b1e408e4004480a9943ea4d912f1ca", + "_type": "cookie_jar" + } + ] +} \ No newline at end of file diff --git a/requests.rest b/requests.rest new file mode 100644 index 0000000..7589771 --- /dev/null +++ b/requests.rest @@ -0,0 +1,56 @@ +GET localhost:8080/things +Accept: application/json + +### +POST localhost:8080/things?type=THING&name=thing3 +Accept: application/json +Content-Type: application/json + +### +GET localhost:8080/things/thing3 +Accept: application/json + +### +DELETE localhost:8080/things/thing1 + +### +POST localhost:8080/things/thing3?type=INPUT&name=input1 +Accept: application/json +Content-Type: application/json + +### +POST localhost:8080/things/thing3?type=INPUT&name=input2 +Accept: application/json +Content-Type: application/json + +### +GET localhost:8080/things/thing3/input2 +Accept: application/json + +### +DELETE localhost:8080/things/thing3/input2 + +### +POST localhost:8080/things/thing3?type=OUTPUT&name=output1 +Accept: application/json +Content-Type: application/json + +### +POST localhost:8080/things/thing3?type=OUTPUT&name=output2 +Accept: application/json +Content-Type: application/json + +### +GET localhost:8080/things/thing3/output2 +Accept: application/json + +### +PUT localhost:8080/things/thing3/output2 +Content-Type: application/json + +### +GET localhost:8080/things/thing3/output2 +Accept: application/json + +### +DELETE localhost:8080/things/thing3/output2 diff --git a/requests.sh b/requests.sh new file mode 100644 index 0000000..22f2062 --- /dev/null +++ b/requests.sh @@ -0,0 +1,43 @@ +curl -X GET --location "http://localhost:8080/things" \ + -H "Accept: application/json" + +curl -X POST --location "http://localhost:8080/things?type=THING&name=thing3" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" + +curl -X GET --location "http://localhost:8080/things/thing3" \ + -H "Accept: application/json" + +curl -X DELETE --location "http://localhost:8080/things/thing1" + +curl -X POST --location "http://localhost:8080/things/thing3?type=INPUT&name=input1" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" + +curl -X POST --location "http://localhost:8080/things/thing3?type=INPUT&name=input2" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" + +curl -X GET --location "http://localhost:8080/things/thing3/input2" \ + -H "Accept: application/json" + +curl -X DELETE --location "http://localhost:8080/things/thing3/input2" + +curl -X POST --location "http://localhost:8080/things/thing3?type=OUTPUT&name=output1" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" + +curl -X POST --location "http://localhost:8080/things/thing3?type=OUTPUT&name=output2" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" + +curl -X GET --location "http://localhost:8080/things/thing3/output2" \ + -H "Accept: application/json" + +curl -X PUT --location "http://localhost:8080/things/thing3/output2" \ + -H "Content-Type: application/json" + +curl -X GET --location "http://localhost:8080/things/thing3/output2" \ + -H "Accept: application/json" + +curl -X DELETE --location "http://localhost:8080/things/thing3/output2" diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..37c6e37 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'servlet' + diff --git a/src/main/java/servlet/ThingsServlet.java b/src/main/java/servlet/ThingsServlet.java new file mode 100644 index 0000000..d5f3e13 --- /dev/null +++ b/src/main/java/servlet/ThingsServlet.java @@ -0,0 +1,17 @@ +package servlet; + +import things.Things; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; + +@WebServlet(name = "Things", urlPatterns = {"/things", "/things/*"}) +public class ThingsServlet extends HttpServlet { + private final Things things = new Things(); + + public ThingsServlet() { + things.addSampleResources(); + } + + // TODO: Implement the GET, POST, PUT and DELETE methods. +} diff --git a/src/main/java/things/CreationBody.java b/src/main/java/things/CreationBody.java new file mode 100644 index 0000000..29a5928 --- /dev/null +++ b/src/main/java/things/CreationBody.java @@ -0,0 +1,23 @@ +package things; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +class CreationBody { + private final String url; + private final Resource.Type type; + + @JsonCreator + public CreationBody(@JsonProperty("url") String url, @JsonProperty("type") Resource.Type type) { + this.url = url; + this.type = type; + } + + public String getUrl() { + return url; + } + + public Resource.Type getType() { + return type; + } +} diff --git a/src/main/java/things/IO.java b/src/main/java/things/IO.java new file mode 100644 index 0000000..8314ff9 --- /dev/null +++ b/src/main/java/things/IO.java @@ -0,0 +1,38 @@ +package things; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Base class for inputs or outputs. + * + * @see Resource + */ +public abstract class IO extends NamedResource { + private final Thing parent; + + protected IO(Thing parent, String name) { + super(name); + this.parent = parent; + } + + protected Thing getParent() { + return parent; + } + + @Override + public String getURI() { + return parent.getURI() + "/" + getName(); + } + + @Override + public String getJSON() throws JsonProcessingException { + return Things.sharedObjectMapper().writerWithView(JSONViews.IODetail.class).writeValueAsString(this); + } + + /** + * Returns the current value of the input or output. + * + * @return Current value. + */ + abstract public int getValue(); +} diff --git a/src/main/java/things/Input.java b/src/main/java/things/Input.java new file mode 100644 index 0000000..0cd6f84 --- /dev/null +++ b/src/main/java/things/Input.java @@ -0,0 +1,40 @@ +package things; + +import com.fasterxml.jackson.annotation.JsonView; + +/** + * Represents an analog input. + * + * @see IO + */ +public class Input extends IO { + private int value = 0; + private static InputGenerator generator; + + Input(Thing parent, String name) { + super(parent, name); + if (generator == null) { + generator = new InputGenerator(); + } + generator.addInput(this); + } + + @Override + @JsonView(JSONViews.IODetail.class) + public int getValue() { + synchronized (this) { + return value; + } + } + + void setValue(int value) { + synchronized (this) { + this.value = value; + } + } + + @Override + public Type getType() { + return Type.INPUT; + } +} diff --git a/src/main/java/things/InputGenerator.java b/src/main/java/things/InputGenerator.java new file mode 100644 index 0000000..e7883ff --- /dev/null +++ b/src/main/java/things/InputGenerator.java @@ -0,0 +1,48 @@ +package things; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.Random; + +class InputGenerator extends Thread { + private static final int PERIOD = 100; + private static final int MAX_VALUE = 1000; + + private final List> inputs = new ArrayList<>(); + private final Random random = new Random(); + + public InputGenerator() { + random.setSeed(System.currentTimeMillis()); + start(); + } + + public void addInput(Input input) { + if (input != null) { + input.setValue(Math.abs(random.nextInt()) % MAX_VALUE); + inputs.add(new WeakReference<>(input)); + } + } + + public void run() { + while (true) { + ListIterator> i = inputs.listIterator(); + while (i.hasNext()) { + Input input = i.next().get(); + if (input != null) { + int value = input.getValue() + 1; + if (value > MAX_VALUE) value = 0; + input.setValue(value); + } else { + i.remove(); + } + } + try { + sleep(PERIOD); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/things/JSONViews.java b/src/main/java/things/JSONViews.java new file mode 100644 index 0000000..a3307b0 --- /dev/null +++ b/src/main/java/things/JSONViews.java @@ -0,0 +1,8 @@ +package things; + +class JSONViews { + public static class ThingOverview {} + public static class ThingDetail {} + public static class IOOverview {} + public static class IODetail {} +} diff --git a/src/main/java/things/NamedResource.java b/src/main/java/things/NamedResource.java new file mode 100644 index 0000000..0b45bc7 --- /dev/null +++ b/src/main/java/things/NamedResource.java @@ -0,0 +1,14 @@ +package things; + +abstract class NamedResource implements Resource { + private final String name; + + public NamedResource(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } +} diff --git a/src/main/java/things/Output.java b/src/main/java/things/Output.java new file mode 100644 index 0000000..198f51e --- /dev/null +++ b/src/main/java/things/Output.java @@ -0,0 +1,48 @@ +package things; + +import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Represents an analog output. + * + * @see IO + */ +public class Output extends IO { + private int value = 0; + + Output(Thing parent, String name) { + super(parent, name); + } + + @Override + @JsonView(JSONViews.IODetail.class) + public int getValue() { + return value; + } + + /** + * Changes the value of the output. + * + * @param value New value to set the output to. + */ + public void setValue(int value) { + this.value = value; + } + + /** + * Updates the output's value using the passed JSON string. + * + * @param json JSON String. Note that the only field required is "value" of type int. + * @throws JsonProcessingException In case of JSON parse error. + */ + public void setJSON(String json) throws JsonProcessingException { + UpdateBody body = Things.sharedObjectMapper().readValue(json, UpdateBody.class); + setValue(body.getValue()); + } + + @Override + public Type getType() { + return Type.OUTPUT; + } +} diff --git a/src/main/java/things/Resource.java b/src/main/java/things/Resource.java new file mode 100644 index 0000000..1a79ec0 --- /dev/null +++ b/src/main/java/things/Resource.java @@ -0,0 +1,73 @@ +package things; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Represents any resource. This can be a things collection, a thing, an input or an output. + */ +public interface Resource { + /** + * Enumerates all types of resources. + */ + enum Type { + /** + * The resource is a collection of things. + * + * @see things.Things + */ + THINGS, + + /** + * The resource is a thing. + * + * @see things.Thing + */ + THING, + + /** + * The resource is an input. + * + * @see things.Input + */ + INPUT, + + /** + * The resource is an output. + * + * @see things.Output + */ + OUTPUT + } + + /** + * Returns the type of the resource. + * + * @return Type of the resource. + */ + Type getType(); + + /** + * Returns the name of the resource. + * + * @return Name of the resource. + */ + @JsonIgnore + String getName(); + + /** + * Returns the URI of the resource. The URI is unique for every resource object. + * + * @return Resource's URI. + */ + String getURI(); + + /** + * Serializes the resource to a JSON representation. + * + * @return JSON representation of the resource. + * @throws JsonProcessingException In case there was an error generating the JSON description. + */ + @JsonIgnore + String getJSON() throws JsonProcessingException; +} diff --git a/src/main/java/things/ResourceList.java b/src/main/java/things/ResourceList.java new file mode 100644 index 0000000..1a0e3aa --- /dev/null +++ b/src/main/java/things/ResourceList.java @@ -0,0 +1,29 @@ +package things; + +import java.util.ArrayList; + +class ResourceList extends ArrayList { + @Override + public boolean add(T namedResource) { + for (NamedResource element: this) { + if (element.getName().equals(namedResource.getName())) { + return false; + } + } + return super.add(namedResource); + } + + @Override + public void add(int index, T namedResource) { + throw new RuntimeException("Not supported"); + } + + public T find(String name) { + for (T element: this) { + if (element.getName().equals(name)) { + return element; + } + } + return null; + } +} diff --git a/src/main/java/things/Thing.java b/src/main/java/things/Thing.java new file mode 100644 index 0000000..25494b5 --- /dev/null +++ b/src/main/java/things/Thing.java @@ -0,0 +1,92 @@ +package things; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.core.JsonProcessingException; + +import java.util.Collection; + +/** + * Represents a thing. A thing can contain multiple inputs and outputs. + * + * @see Resource + */ +public class Thing extends NamedResource { + private final ResourceList ios = new ResourceList<>(); + + Thing(String name) { + super(name); + } + + /** + * Returns the collection of all input and output resources of the thing. + * + * @return All inputs/outputs. + */ + @JsonProperty("io") + @JsonView(JSONViews.ThingDetail.class) + public Collection getIOs() { + return ios; + } + + /** + * Returns the input or output with the given name or null if no input/output with the given name exists. + * + * @param name Name of the input/output. + * @return Input/output or null if no object with the given name exists. + */ + public IO getIOWithName(String name) { + return ios.find(name); + } + + /** + * Adds an input with the given name. + * If the input could be added, the newly created input is returned. If an object with the given name already exists within the thing, null is returned. + * + * @param name Name of the input to add. + * @return Newly added input, null if input could not be added. + */ + public Input addInputWithName(String name) { + Input input = new Input(this, name); + if (ios.add(input)) { + return input; + } else { + return null; + } + } + + /** + * Adds an output with the given name. + * If the output could be added, the newly created output is returned. If an object with the given name already exists within the thing, null is returned. + * + * @param name Name of the output to add. + * @return Newly added output, null if input could not be added. + */ + public Output addOutputWithName(String name) { + Output output = new Output(this, name); + if (ios.add(output)) { + return output; + } else { + return null; + } + } + + ResourceList getIOResources() { + return ios; + } + + @Override + public Type getType() { + return Type.THING; + } + + @Override + public String getURI() { + return "/things/" + getName(); + } + + @Override + public String getJSON() throws JsonProcessingException { + return Things.sharedObjectMapper().writerWithView(JSONViews.ThingDetail.class).writeValueAsString(this); + } +} diff --git a/src/main/java/things/Things.java b/src/main/java/things/Things.java new file mode 100644 index 0000000..cea92bf --- /dev/null +++ b/src/main/java/things/Things.java @@ -0,0 +1,221 @@ +package things; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import java.util.Arrays; +import java.util.Collection; + +/** + * Allows to store a hierarchy of resources. + */ +public class Things extends NamedResource { + private final static ObjectMapper objectMapper = new ObjectMapper(); + + static ObjectMapper sharedObjectMapper() { + return objectMapper; + } + + private final ResourceList things = new ResourceList<>(); + + /** + * Creates a new Things instance that can be used to save Thing, Input and Output resources. + * + * Note that this is the only class your servlet has to interact directly with. It has all the functions needed to create, find, modify and delete all kind of resources. + */ + public Things() { + super("things"); + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + } + + /** + * Returns the collection of all things contained in this collection. + * + * @return All Things part of the collection. + */ + public Collection getThings() { + return things; + } + + /** + * Returns the thing with the given name or null if no such thing exists. + * + * @param name Name of the thing to look for. + * @return The Thing instance with the given name or null if no such thing exists. + */ + public Thing getThingWithName(String name) { + return things.find(name); + } + + /** + * Tries to add a new Thing instance with the given name and returns the instance just added. If there is already a thing with the same name, no new thing will be added and null is returned + * instead. + * + * @param name Name of the thing to add. + * @return The newly created thing or null if a thing with the same name already exists. + */ + public Thing addThingWithName(String name) { + Thing thing = new Thing(name); + if (things.add(thing)) { + return thing; + } else { + return null; + } + } + + /** + * Tries to find the resource identified by the given URI. Returns the resource or null if the resource could not be found. + * + * @param uri URI of the resource to look for. + * @return Resource instance or null if no resource exists at the given URI. + */ + public Resource getResourceByURI(String uri) { + String[] components = parseURI(uri); + + if (components.length == 0 || !components[0].equals("things")) { + return null; + } + + switch (components.length) { + case 1: + return this; + + case 2: + return things.find(components[1]); + + case 3: + Thing thing = things.find(components[1]); + if (thing == null) { + return null; + } + return thing.getIOResources().find(components[2]); + + default: + return null; + } + } + + /** + * Creates a new child resource to the parent resource at the given URI of the given type. Returns the created resource or null if the resource could not be created. + * + * @param parentResourceURI URI of the parent resource to which the new resource should be added. + * @param childName Name for the new child resource. + * @param childType Type of the child resource to create. + * @return The child resource created or null if the resource could not be created. + */ + public Resource addChildToResourceWithURI(String parentResourceURI, String childName, Resource.Type childType) { + + // Get the root resource to which the item should be added. + Resource resource = getResourceByURI(parentResourceURI); + + // Ensure that this resource exists. + if (resource == null) { + return null; + } + + // For each different type of parent resource, there is another action to take. + switch (resource.getType()) { + case THINGS: + + // Try to add a new thing. + return addThingWithName(childName); + + case THING: + Thing thing = (Thing)resource; + + // Check what type of resource has to be added to the thing. + switch (childType) { + case INPUT: + + // Try to add a new input. + return thing.addInputWithName(childName); + + case OUTPUT: + + // Try to add a new output. + return thing.addOutputWithName(childName); + + default: + + // Fail if unsupported type of object has to be created. + return null; + } + + case INPUT: + case OUTPUT: + + // Fail, input and outputs do not have child resources. + return null; + + default: + + // If the parent resource is of unknown type, fail. + return null; + } + } + + /** + * Removes the resource (whatever type it is) from the resource model. All it's children will be removed too. + * + * @param uri URI of the resource to remove. + * @return The resource if it exists and could be removed, null otherwise. + */ + public Resource removeResourceByURI(String uri) { + Resource resource = getResourceByURI(uri); + + if (resource == null) { + return null; + } + + switch (resource.getType()) { + case THING: + things.remove(resource); + return resource; + + case INPUT: + case OUTPUT: + ((IO)resource).getParent().getIOResources().remove(resource); + return resource; + + default: + return null; + } + } + + /** + * Adds sample resources to the collection. + */ + public void addSampleResources() { + things.clear(); + addChildToResourceWithURI("/things", "thing1", Type.THING); + addChildToResourceWithURI("/things/thing1", "input1", Type.INPUT); + addChildToResourceWithURI("/things/thing1", "output1", Type.OUTPUT); + addChildToResourceWithURI("/things", "thing2", Type.THING); + addChildToResourceWithURI("/things/thing2", "input1", Type.INPUT); + addChildToResourceWithURI("/things/thing2", "output1", Type.OUTPUT); + } + + @Override + public Type getType() { + return Type.THINGS; + } + + @Override + public String getURI() { + return "/things"; + } + + @Override + public String getJSON() throws JsonProcessingException { + return objectMapper.writerWithView(JSONViews.ThingOverview.class).writeValueAsString(things); + } + + private String[] parseURI(String uri) { + String[] components = uri.split("/", 0); + if (components.length > 0 && components[0].isEmpty()) { + components = Arrays.copyOfRange(components, 1, components.length); + } + return components; + } +} diff --git a/src/main/java/things/UpdateBody.java b/src/main/java/things/UpdateBody.java new file mode 100644 index 0000000..72c710a --- /dev/null +++ b/src/main/java/things/UpdateBody.java @@ -0,0 +1,17 @@ +package things; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +class UpdateBody { + private final int value; + + @JsonCreator + UpdateBody(@JsonProperty("value") int value) { + this.value = value; + } + + int getValue() { + return value; + } +}