diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b49eb5d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI-Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +concurrency: + group: ci_${{ github.ref }} + cancel-in-progress: true + +env: + javaVersion: "17" + javaDistribution: "liberica" + +jobs: + ci-platform-aws: + runs-on: ubuntu-latest + needs: + - s3 + steps: + - run: echo "CI-Build completed!" + + s3: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + java-version: ${{ env.javaVersion }} + distribution: ${{ env.javaDistribution }} + - run: | + chmod +x gradlew + ./gradlew :examples:s3:build + - uses: actions/upload-artifact@v3 + if: always() + with: + name: test-results_s3 + path: "**/build/reports/tests" diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..8233ee8 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,65 @@ +import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension +import org.gradle.api.file.DuplicatesStrategy.INCLUDE +import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED +import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("org.springframework.boot") version "3.1.0" apply false + id("io.spring.dependency-management") version "1.1.0" apply false + id("org.asciidoctor.jvm.convert") version "3.3.2" apply false + + kotlin("jvm") version "1.8.21" apply false + kotlin("plugin.spring") version "1.8.21" apply false + kotlin("plugin.jpa") version "1.8.21" apply false + kotlin("plugin.noarg") version "1.8.21" apply false +} + +allprojects { + repositories { mavenCentral(); mavenLocal() } + + if (project.childProjects.isNotEmpty()) return@allprojects + + apply { + plugin("io.spring.dependency-management") + } + + the().apply { + imports { + mavenBom("org.jetbrains.kotlin:kotlin-bom:1.8.21") + mavenBom("org.testcontainers:testcontainers-bom:1.18.3") + mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) + } + + dependencies { + dependency("com.ninja-squad:springmockk:4.0.2") + dependency("io.mockk:mockk-jvm:1.13.5") + dependency("org.testcontainers:junit-jupiter:1.18.1") + dependency("org.testcontainers:localstack:1.18.1") + dependency("com.amazonaws:aws-java-sdk-s3:1.12.272") + dependency("com.amazonaws:aws-java-sdk-sts:1.12.272") + dependency("io.kotest:kotest-assertions-core:5.6.2") + } + } + + tasks { + withType { duplicatesStrategy = INCLUDE } + withType { duplicatesStrategy = INCLUDE } + withType { + sourceCompatibility = "17" + targetCompatibility = "17" + } + withType { + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = "17" + incremental = false + } + } + withType { + group = "verification" + useJUnitPlatform() + testLogging { events(FAILED, SKIPPED) } + } + } +} diff --git a/examples/s3/build.gradle.kts b/examples/s3/build.gradle.kts new file mode 100644 index 0000000..955617c --- /dev/null +++ b/examples/s3/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("org.springframework.boot") + id("io.spring.dependency-management") + + kotlin("jvm") + kotlin("plugin.spring") +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.testcontainers:localstack") + implementation("com.amazonaws:aws-java-sdk-s3") + implementation("com.amazonaws:aws-java-sdk-sts") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("io.mockk:mockk-jvm") + testImplementation("com.ninja-squad:springmockk") + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("io.kotest:kotest-assertions-core") +} diff --git a/examples/s3/http/S3.http b/examples/s3/http/S3.http new file mode 100644 index 0000000..bfcf637 --- /dev/null +++ b/examples/s3/http/S3.http @@ -0,0 +1,19 @@ +### Get Clean Code Cover +GET localhost:8080/books/{{clean-code}}/cover + +### Get Lord of the Rings Cover +GET localhost:8080/books/{{lord-of-the-rings}}/cover + +### Get Mythical Man Month Cover +### Will fail at first because the cover has to be added by the PUT request below. +GET localhost:8080/books/{{mythical-man-moth}}/cover + +### Put Mythical Man Month Cover +PUT localhost:8080/books/{{mythical-man-moth}}/cover +Content-Type: multipart/form-data; boundary=boundary + +--boundary +Content-Disposition: form-data; name="cover"; filename="mythical-man-month.jpg" +Content-Type: image/jpeg + +< ../src/main/resources/bookcovers/mythical-man-month.jpg diff --git a/examples/s3/http/http-client.env.json b/examples/s3/http/http-client.env.json new file mode 100644 index 0000000..730007a --- /dev/null +++ b/examples/s3/http/http-client.env.json @@ -0,0 +1,7 @@ +{ + "ldev": { + "clean-code": "73a3a94e-9ec7-4c35-82ff-9235fa4a2a86", + "lord-of-the-rings": "81abeacb-9850-4691-94b8-2fff9bb7b1a7", + "mythical-man-moth": "021e0ea7-6fda-4682-9747-eb7161154885" + } +} \ No newline at end of file diff --git a/examples/s3/src/main/kotlin/example/aws/s3/Application.kt b/examples/s3/src/main/kotlin/example/aws/s3/Application.kt new file mode 100644 index 0000000..5a268e9 --- /dev/null +++ b/examples/s3/src/main/kotlin/example/aws/s3/Application.kt @@ -0,0 +1,17 @@ +package example.aws.s3 + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.builder.SpringApplicationBuilder +import org.springframework.boot.runApplication + +@SpringBootApplication +class Application + +/** + * Start the application from a builder (instead of the usual [runApplication]) so we can supply our [S3Initializer]. + */ +fun main(args: Array) { + SpringApplicationBuilder(Application::class.java) + .initializers(S3Initializer()) + .run(*args) +} diff --git a/examples/s3/src/main/kotlin/example/aws/s3/ApplicationConfiguration.kt b/examples/s3/src/main/kotlin/example/aws/s3/ApplicationConfiguration.kt new file mode 100644 index 0000000..f5120ba --- /dev/null +++ b/examples/s3/src/main/kotlin/example/aws/s3/ApplicationConfiguration.kt @@ -0,0 +1,90 @@ +package example.aws.s3 + +import com.amazonaws.auth.AWSStaticCredentialsProvider +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.client.builder.AwsClientBuilder +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.AmazonS3ClientBuilder +import example.aws.s3.domain.S3Properties +import org.springframework.boot.context.event.ApplicationStartedEvent +import org.springframework.context.ApplicationContextInitializer +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.event.EventListener +import org.springframework.context.support.GenericApplicationContext +import org.testcontainers.containers.localstack.LocalStackContainer +import org.testcontainers.utility.DockerImageName +import java.io.File +import java.util.function.Supplier + +@Configuration +class ApplicationConfiguration( + + /** + * There is no real [AmazonS3] bean available to us, we artificially create and inject our own using the + * [S3Initializer] below. IntelliJ does not know that, so we have to suppress its inspections about missing beans. + */ + @Suppress("SpringJavaInjectionPointsAutowiringInspection") + private val s3: AmazonS3, + private val s3properties: S3Properties, +) { + + /** + * Our S3 is not permanent, but is instead re-created every time the app is running. Therefore, we also have to + * create a bucket every time the container starts. + * + * We also use this opportunity to add 2 book covers into the bucket to serve as pre-made examples. + */ + @EventListener + fun createBucketAddExamples(event: ApplicationStartedEvent) { + s3.createBucket(s3properties.bucketName) + + val cleanCodeCover = File(this::class.java.getResource("/bookcovers/clean_code.jpg")!!.path) + val lotrCover = File(this::class.java.getResource("/bookcovers/fellowship_of_the_ring.jpg")!!.path) + + s3.putObject(s3properties.bucketName, s3properties.books.cleanCode, cleanCodeCover) + s3.putObject(s3properties.bucketName, s3properties.books.lordOfTheRings, lotrCover) + } +} + +/** + * This [ApplicationContextInitializer] is the means by which we create an S3 instance, both for our tests, and also as + * a substitute for not having a real AWS environment in this simple example. + */ +class S3Initializer : ApplicationContextInitializer { + + override fun initialize(applicationContext: GenericApplicationContext) { + val container = S3Container().withServices(LocalStackContainer.Service.S3).apply { start() } + createS3Bean(container, applicationContext) + } + + /** + * Normally you would declare a [Bean] method where you create and configure your [AmazonS3] client. Since we use + * a Test Container we instead inject our client directly in Spring's application context. + */ + private fun createS3Bean( + container: LocalStackContainer, + applicationContext: GenericApplicationContext + ) { + applicationContext.registerBean( + AmazonS3::class.java.simpleName, + AmazonS3::class.java, + Supplier { + AmazonS3ClientBuilder + .standard() + .withEndpointConfiguration( + AwsClientBuilder.EndpointConfiguration( + container.getEndpointOverride(LocalStackContainer.Service.S3).toString(), + container.region + ) + ) + .withCredentials( + AWSStaticCredentialsProvider(BasicAWSCredentials(container.accessKey, container.secretKey)) + ) + .build() + } + ) + } + + private class S3Container : LocalStackContainer(DockerImageName.parse("localstack/localstack:0.11.3")) +} diff --git a/examples/s3/src/main/kotlin/example/aws/s3/api/BookCoverController.kt b/examples/s3/src/main/kotlin/example/aws/s3/api/BookCoverController.kt new file mode 100644 index 0000000..a07ea0f --- /dev/null +++ b/examples/s3/src/main/kotlin/example/aws/s3/api/BookCoverController.kt @@ -0,0 +1,49 @@ +package example.aws.s3.api + +import example.aws.s3.domain.BookCoverData +import example.aws.s3.domain.BookCoverService +import org.springframework.core.io.InputStreamResource +import org.springframework.core.io.Resource +import org.springframework.http.HttpStatus.CREATED +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.http.ResponseEntity.notFound +import org.springframework.http.ResponseEntity.ok +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile +import java.util.* + +/** + * Simple REST controller that allows to either GET or PUT a cover for a specific book. + */ +@RestController +class BookCoverController( + private val bookCoverService: BookCoverService +) { + + @GetMapping("/books/{id}/cover") + fun getCover(@PathVariable("id") id: UUID): ResponseEntity { + val coverData: BookCoverData = bookCoverService.findCover(id) + ?: return notFound().build() + + return ok() + .contentLength(coverData.contentLength) + .contentType(MediaType.parseMediaType(coverData.contentType)) + .body(InputStreamResource(coverData.byteStream)) + } + + @ResponseStatus(CREATED) + @PutMapping("/books/{id}/cover") + fun putCover( + @PathVariable("id") id: UUID, + @RequestParam("cover") cover: MultipartFile, + ) { + bookCoverService.saveCover(id, cover) + } + +} \ No newline at end of file diff --git a/examples/s3/src/main/kotlin/example/aws/s3/domain/BookCoverService.kt b/examples/s3/src/main/kotlin/example/aws/s3/domain/BookCoverService.kt new file mode 100644 index 0000000..d2d8c22 --- /dev/null +++ b/examples/s3/src/main/kotlin/example/aws/s3/domain/BookCoverService.kt @@ -0,0 +1,63 @@ +package example.aws.s3.domain + +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.model.AmazonS3Exception +import com.amazonaws.services.s3.model.ObjectMetadata +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.http.HttpStatus.NOT_FOUND +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.util.* + +/** + * Business class that implements the details of loading and saving book covers. Offers a clean api that hides the + * lower level details of the S3 sdk. + */ +@Service +@Suppress("SpringJavaInjectionPointsAutowiringInspection") +@EnableConfigurationProperties(S3Properties::class) +class BookCoverService( + private val s3: AmazonS3, + private val s3Properties: S3Properties, +) { + + fun findCover(id: UUID): BookCoverData? { + val s3Object = try { + s3.getObject(s3Properties.bucketName, id.toString()) + } catch (e: AmazonS3Exception) { + when (e.statusCode) { + NOT_FOUND.value() -> return null + else -> throw e + } + } + return BookCoverData( + byteStream = s3Object.objectContent, + contentLength = s3Object.objectMetadata.contentLength, + contentType = s3Object.objectMetadata.contentType, + ) + } + + fun saveCover( + id: UUID, + cover: MultipartFile, + ) { + s3.putObject( + s3Properties.bucketName, + id.toString(), + cover.inputStream, + ObjectMetadata().also { + it.contentLength = cover.size + it.contentType = cover.contentType + } + ) + } + +} + +data class BookCoverData( + val byteStream: InputStream, + val contentLength: Long, + val contentType: String, +) diff --git a/examples/s3/src/main/kotlin/example/aws/s3/domain/S3Properties.kt b/examples/s3/src/main/kotlin/example/aws/s3/domain/S3Properties.kt new file mode 100644 index 0000000..78116de --- /dev/null +++ b/examples/s3/src/main/kotlin/example/aws/s3/domain/S3Properties.kt @@ -0,0 +1,17 @@ +package example.aws.s3.domain + +import org.springframework.boot.context.properties.ConfigurationProperties + +/** + * Contains the name of our bucket as well as predefined book ids we use as example data. + */ +@ConfigurationProperties(prefix = "example.s3") +data class S3Properties( + val bucketName: String, + val books: Books, +) { + data class Books( + val cleanCode: String, + val lordOfTheRings: String, + ) +} \ No newline at end of file diff --git a/examples/s3/src/main/resources/application.yaml b/examples/s3/src/main/resources/application.yaml new file mode 100644 index 0000000..04661b4 --- /dev/null +++ b/examples/s3/src/main/resources/application.yaml @@ -0,0 +1,7 @@ +#file: noinspection SpringBootApplicationYaml +example: + s3: + bucket-name: "example-bucket" + books: + clean-code: "73a3a94e-9ec7-4c35-82ff-9235fa4a2a86" + lord-of-the-rings: "81abeacb-9850-4691-94b8-2fff9bb7b1a7" diff --git a/examples/s3/src/main/resources/bookcovers/clean_code.jpg b/examples/s3/src/main/resources/bookcovers/clean_code.jpg new file mode 100644 index 0000000..e65df9a Binary files /dev/null and b/examples/s3/src/main/resources/bookcovers/clean_code.jpg differ diff --git a/examples/s3/src/main/resources/bookcovers/fellowship_of_the_ring.jpg b/examples/s3/src/main/resources/bookcovers/fellowship_of_the_ring.jpg new file mode 100644 index 0000000..f88f078 Binary files /dev/null and b/examples/s3/src/main/resources/bookcovers/fellowship_of_the_ring.jpg differ diff --git a/examples/s3/src/main/resources/bookcovers/mythical-man-month.jpg b/examples/s3/src/main/resources/bookcovers/mythical-man-month.jpg new file mode 100644 index 0000000..7f0b4bf Binary files /dev/null and b/examples/s3/src/main/resources/bookcovers/mythical-man-month.jpg differ diff --git a/examples/s3/src/test/kotlin/example/aws/s3/domain/BookCoverServiceTest.kt b/examples/s3/src/test/kotlin/example/aws/s3/domain/BookCoverServiceTest.kt new file mode 100644 index 0000000..c927f9e --- /dev/null +++ b/examples/s3/src/test/kotlin/example/aws/s3/domain/BookCoverServiceTest.kt @@ -0,0 +1,85 @@ +package example.aws.s3.domain + +import example.aws.s3.S3Initializer +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.annotation.ComponentScan +import org.springframework.http.MediaType.IMAGE_JPEG_VALUE +import org.springframework.http.MediaType.IMAGE_PNG_VALUE +import org.springframework.mock.web.MockMultipartFile +import org.springframework.test.context.ContextConfiguration +import java.util.UUID.randomUUID +import kotlin.annotation.AnnotationTarget.CLASS + +@Retention +@Target(CLASS) +@ContextConfiguration(initializers = [S3Initializer::class]) +annotation class RunWithDockerizedS3 + +/** + * Test that boots the business logic slice of the application and uses LocalStack to also start a locally running S3 + * bucket. + */ +@SpringBootTest +@ComponentScan(basePackageClasses = [BookCoverService::class]) +@RunWithDockerizedS3 +class BookCoverServiceTest(@Autowired private val cut: BookCoverService) { + + private val id = randomUUID() + + @Test + fun `returns null when there are no covers`() { + val result = cut.findCover(id) + + result shouldBe null + } + + @Test + fun `returns null when there is no cover for the given id`() { + val cover = MockMultipartFile( + id.toString(), "testfile.jpeg", IMAGE_JPEG_VALUE, "123".toByteArray() + ) + cut.saveCover(id, cover) + + val result = cut.findCover(randomUUID()) + + result shouldBe null + } + + @Test + fun `correctly finds saved cover`() { + val cover = MockMultipartFile( + id.toString(), "testfile.jpeg", IMAGE_JPEG_VALUE, "123".toByteArray() + ) + cut.saveCover(id, cover) + + val result = cut.findCover(id) + + result!! + result.contentType shouldBe IMAGE_JPEG_VALUE + result.contentLength shouldBe 3 + result.byteStream.readAllBytes() shouldBe "123".toByteArray() + } + + @Test + fun `correctly updates saved cover`() { + val cover1 = MockMultipartFile( + id.toString(), "testfile.jpeg", IMAGE_JPEG_VALUE, "123".toByteArray() + ) + val cover2 = MockMultipartFile( + id.toString(), "testfile.png", IMAGE_PNG_VALUE, "45678".toByteArray() + ) + cut.saveCover(id, cover1) + cut.saveCover(id, cover2) + + val result = cut.findCover(id) + + result!! + result.contentType shouldBe IMAGE_PNG_VALUE + result.contentLength shouldBe 5 + result.byteStream.readAllBytes() shouldBe "45678".toByteArray() + } + +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..2964a16 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.workers.max=5 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1ce6e58 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..603ff56 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Feb 06 12:27:20 CET 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4453cce --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..d892db1 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,3 @@ +rootProject.name = "cnt-platform-aws" + +include("examples:s3")