diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index c8f45861476c0..39b40c78436bd 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -74,6 +74,12 @@ public class CreateProjectMojo extends AbstractMojo { @Parameter(property = "projectVersion") private String projectVersion; + @Parameter(property = "codestartsEnabled") + private Boolean codestartsEnabled; + + @Parameter(property = "withExampleCode") + private Boolean withExampleCode; + /** * Group ID of the target platform BOM */ @@ -190,7 +196,9 @@ public void execute() throws MojoExecutionException { .version(projectVersion) .sourceType(sourceType) .className(className) - .extensions(extensions); + .extensions(extensions) + .codestartsEnabled(codestartsEnabled) + .withExampleCode(withExampleCode); if (path != null) { createProject.setValue("path", path); } diff --git a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptor.java b/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptor.java index d346fd6fd0759..2fd03b68f11fc 100644 --- a/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptor.java +++ b/devtools/platform-descriptor-json/src/main/java/io/quarkus/platform/descriptor/loader/json/impl/QuarkusJsonPlatformDescriptor.java @@ -16,6 +16,7 @@ import io.quarkus.dependencies.Extension; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import io.quarkus.platform.descriptor.ResourceInputStreamConsumer; +import io.quarkus.platform.descriptor.ResourcePathConsumer; import io.quarkus.platform.descriptor.loader.json.ResourceLoader; import io.quarkus.platform.tools.DefaultMessageWriter; import io.quarkus.platform.tools.MessageWriter; @@ -126,6 +127,15 @@ public T loadResource(String name, ResourceInputStreamConsumer consumer) return resourceLoader.loadResource(name, consumer); } + @Override + public T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException { + getLog().debug("Loading Quarkus platform resource %s", name); + if (resourceLoader == null) { + throw new IllegalStateException("Resource loader has not been provided"); + } + return resourceLoader.loadResourcePath(name, consumer); + } + @Override public List getCategories() { return categories; diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/build-layout.r-qute-include b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/build-layout.r-qute-include new file mode 100644 index 0000000000000..3fd4b6b513de0 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/build-layout.r-qute-include @@ -0,0 +1,37 @@ +{#insert plugins} +plugins { + id '{quarkus.plugin.id}' +} +{/} + +{#insert repositories} +repositories { + mavenLocal() + mavenCentral() +} +{/} + +{#insert dependencies} +dependencies { + implementation enforcedPlatform("$\{quarkusPlatformGroupId}:$\{quarkusPlatformArtifactId}:$\{quarkusPlatformVersion}") +{#for dep in dependencies} + implementation '{dep}' +{/for} + testImplementation 'io.quarkus:quarkus-junit5' +{#for dep in test-dependencies} + testImplementation '{dep}' +{/for} +} +{/} + +{#insert project} +group '{project.group-id}' +version '{project.version}' +{/} + +{#insert java} +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} +{/} diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/gradle.r-qute.properties b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/gradle.r-qute.properties new file mode 100644 index 0000000000000..72c7052dddd09 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/gradle.r-qute.properties @@ -0,0 +1,6 @@ +#Gradle properties +#Tue Jun 16 08:41:21 UTC 2020 +quarkusPluginVersion={quarkus.plugin.version} +quarkusPlatformGroupId={quarkus.platform.group-id} +quarkusPlatformArtifactId={quarkus.platform.artifact-id} +quarkusPlatformVersion={quarkus.platform.version} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/settings.r-qute.gradle b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/settings.r-qute.gradle new file mode 100644 index 0000000000000..adce284138546 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/base/settings.r-qute.gradle @@ -0,0 +1,11 @@ +pluginManagement { + repositories { + mavenLocal() + mavenCentral() + gradlePluginPortal() + } + plugins { + id '{quarkus.plugin.id}' version "$\{quarkusPluginVersion}" + } +} +rootProject.name='{project.artifact-id}' diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/codestart.yml new file mode 100644 index 0000000000000..531f4497746ce --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/codestart.yml @@ -0,0 +1,31 @@ +name: gradle +type: buildtool +spec: + base: + data: + quarkus: + plugin: + id: io.quarkus + version: + kotlin: 1.3.72 + scala: 2.12.8 + shared-data: + buildtool: + build-dir: build + guide: https://quarkus.io/guides/gradle-tooling#building-a-native-executable + cmd: + dev: ./gradlew quarkusDev + package: ./gradlew quarkusBuild + package-uberjar: ./gradlew quarkusBuild --uber-jar + package-native: ./gradlew build -Dquarkus.package.type=native + package-native-container: ./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true + gitignore: > + # Gradle + .gradle/ + build/ + kotlin: + dependencies: + - org.jetbrains.kotlin:kotlin-stdlib-jdk8 + scala: + dependencies: + - org.scala-lang:scala-library:${version.scala} diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/java/build.r-qute.gradle b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/java/build.r-qute.gradle new file mode 100644 index 0000000000000..185ccdd49c664 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/java/build.r-qute.gradle @@ -0,0 +1,17 @@ +{#include build-layout} +{#plugins} +plugins { + id 'java' + id '{quarkus.plugin.id}' +} +{/plugins} +{/include} + +compileJava { + options.encoding = 'UTF-8' + options.compilerArgs << '-parameters' +} + +compileTestJava { + options.encoding = 'UTF-8' +} diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/kotlin/build.r-qute.gradle b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/kotlin/build.r-qute.gradle new file mode 100644 index 0000000000000..b38ff1c2ceabf --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/kotlin/build.r-qute.gradle @@ -0,0 +1,32 @@ +{#include build-layout} +{#plugins} +plugins { + id 'org.jetbrains.kotlin.jvm' version "{version.kotlin}" + id "org.jetbrains.kotlin.plugin.allopen" version "{version.kotlin}" + id '{quarkus.plugin.id}' +} +{/plugins} +{/include} + +quarkus { + setOutputDirectory("$projectDir/build/classes/kotlin/main") +} + +quarkusDev { + setSourceDir("$projectDir/src/main/kotlin") +} + +allOpen { + annotation("javax.ws.rs.Path") + annotation("javax.enterprise.context.ApplicationScoped") + annotation("io.quarkus.test.junit.QuarkusTest") +} + +compileKotlin { + kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8 + kotlinOptions.javaParameters = true +} + +compileTestKotlin { + kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8 +} diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/scala/build.r-qute.gradle b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/scala/build.r-qute.gradle new file mode 100644 index 0000000000000..5ad817212fd10 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/gradle/scala/build.r-qute.gradle @@ -0,0 +1,15 @@ +{#include build-layout} +{#plugins} +plugins { + id 'scala' + id '{quarkus.plugin.id}' +} +{/plugins} +{/include} + +compileScala { + scalaCompileOptions.encoding = 'UTF-8' + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/base/pom.r-qute.xml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/base/pom.r-qute.xml new file mode 100644 index 0000000000000..90cf51d236b02 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/base/pom.r-qute.xml @@ -0,0 +1,138 @@ + + 4.0.0 + {project.group-id} + {project.artifact-id} + {project.version} + + + UTF-8 + UTF-8 + 1.8 + 1.8 + true + + {quarkus.platform.group-id} + {quarkus.platform.artifact-id} + {quarkus.platform.version} + {quarkus.plugin.version} + {maven.version.maven-compiler-plugin} + {maven.version.maven-surefire-plugin} + + + + + + $\{quarkus.platform.group-id} + $\{quarkus.platform.artifact-id} + $\{quarkus.platform.version} + pom + import + + + + + + {#each dependencies} + + {it.groupId} + {it.artifactId} + {#if it.version} + {it.version} + {/if} + + {/each} + + + + io.quarkus + quarkus-junit5 + test + + {#each test-dependencies} + + {it.groupId} + {it.artifactId} + {#if it.version} + {it.version} + {/if} + test + + {/each} + + + + + + + {quarkus.plugin.group-id} + {quarkus.plugin.artifact-id} + $\{quarkus-plugin.version} + + + + build + + + + + + maven-compiler-plugin + $\{compiler-plugin.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + $\{surefire-plugin.version} + + + org.jboss.logmanager.LogManager + $\{maven.home} + + + + + + + + + + native + + + native + + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + $\{surefire-plugin.version} + + + + integration-test + verify + + + + $\{project.build.directory}/$\{project.build.finalName}-runner + org.jboss.logmanager.LogManager + $\{maven.home} + + + + + + + + + + + diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/codestart.yml new file mode 100644 index 0000000000000..9a158d3eca568 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/codestart.yml @@ -0,0 +1,31 @@ +--- +name: maven +type: buildtool +fallback: true +spec: + base: + data: + maven: + version: + kotlin: 1.3.72 + scala: 2.12.8 + scala-maven-plugin: 4.1.1 + maven-compiler-plugin: 3.8.1 + maven-surefire-plugin: 2.22.1 + shared-data: + buildtool: + build-dir: target + guide: https://quarkus.io/guides/building-native-image + cmd: + dev: ./mvnw compile quarkus:dev + package: ./mvnw package + package-uberjar: ./mvnw package -PuberJar + package-native: ./mvnw package -Pnative + package-native-container: ./mvnw package -Pnative -Dquarkus.native.container-build=true + gitignore: | + #Maven + target/ + pom.xml.tag + pom.xml.releaseBackup + pom.xml.versionsBackup + release.properties diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/kotlin/pom.w-pom-merge.r-qute.xml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/kotlin/pom.w-pom-merge.r-qute.xml new file mode 100644 index 0000000000000..f87cc6b6345bb --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/kotlin/pom.w-pom-merge.r-qute.xml @@ -0,0 +1,67 @@ + + + {maven.version.kotlin} + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + + io.rest-assured + kotlin-extensions + test + + + + src/main/kotlin + src/test/kotlin + + + + kotlin-maven-plugin + org.jetbrains.kotlin + $\{kotlin.version} + + + compile + + compile + + + + test-compile + + test-compile + + + + + true + 1.8 + + + all-open + + + + + + + + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + $\{kotlin.version} + + + + + + \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/scala/pom.w-pom-merge.r-qute.xml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/scala/pom.w-pom-merge.r-qute.xml new file mode 100644 index 0000000000000..00c9283819430 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/buildtool/maven/scala/pom.w-pom-merge.r-qute.xml @@ -0,0 +1,62 @@ + + + {maven.version.scala-maven-plugin} + {maven.version.scala} + + + + io.quarkus + quarkus-scala + + + org.scala-lang + scala-library + $\{scala.version} + + + org.scala-lang + scala-reflect + $\{scala.version} + + + + src/main/scala + src/test/scala + + + net.alchim31.maven + scala-maven-plugin + $\{scala-maven-plugin.version} + + + scala-compile-first + process-resources + + add-source + compile + + + + scala-test-compile + process-test-resources + + add-source + testCompile + + + + + $\{scala.version} + + -deprecation + -feature + -explaintypes + -target:jvm-1.8 + -Ypartial-unification + + + + + + \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/config/properties/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/config/properties/codestart.yml new file mode 100644 index 0000000000000..d234a9f108632 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/config/properties/codestart.yml @@ -0,0 +1,9 @@ +--- +name: config-properties +type: config +fallback: true +spec: + base: + shared-data: + config: + file-name: application.properties \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/config/yaml/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/config/yaml/codestart.yml new file mode 100644 index 0000000000000..be8014c57d590 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/config/yaml/codestart.yml @@ -0,0 +1,8 @@ +--- +name: config-yaml +type: config +spec: + base: + shared-data: + config: + file-name: application.yml \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/base/README.w-append.md b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/base/README.w-append.md new file mode 100644 index 0000000000000..85b52a94b6f5b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/base/README.w-append.md @@ -0,0 +1,3 @@ +# Command Mode + +Guide: https://quarkus.io/guides/command-mode-reference diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/codestart.yml new file mode 100644 index 0000000000000..f8be55442367c --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/codestart.yml @@ -0,0 +1,10 @@ +--- +name: commandmode-example +ref: commandmode +type: example +spec: + base: + data: + greeting: + message: "hello" + default-name: "commando" diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/java/src/main/java/org/acme/commandmode/GreetingMain.r-qute.java b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/java/src/main/java/org/acme/commandmode/GreetingMain.r-qute.java new file mode 100644 index 0000000000000..f1f4b7f4ce7d2 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/java/src/main/java/org/acme/commandmode/GreetingMain.r-qute.java @@ -0,0 +1,27 @@ +package org.acme.commandmode; + +import javax.enterprise.context.control.ActivateRequestContext; +import javax.inject.Inject; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.QuarkusApplication; +import io.quarkus.runtime.annotations.QuarkusMain; + +@QuarkusMain +public class GreetingMain implements QuarkusApplication { + + @Inject + GreetingService service; + + @Override + public int run(String... args) throws Exception { + + if(args.length>0) { + System.out.println(service.greeting(String.join(" ", args))); + } else { + System.out.println(service.greeting("{greeting.default-name}")); + } + + return 0; + } +} diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/java/src/main/java/org/acme/commandmode/GreetingService.r-qute.java b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/java/src/main/java/org/acme/commandmode/GreetingService.r-qute.java new file mode 100644 index 0000000000000..08aee0c573065 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/example/commandmode-example/java/src/main/java/org/acme/commandmode/GreetingService.r-qute.java @@ -0,0 +1,12 @@ +package org.acme.commandmode; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class GreetingService { + + public String greeting(String name) { + return "{greeting.message} " + name; + } + +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/java/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/java/codestart.yml new file mode 100644 index 0000000000000..82534621bb166 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/java/codestart.yml @@ -0,0 +1,4 @@ +--- +name: java +type: language +fallback: true \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/kotlin/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/kotlin/codestart.yml new file mode 100644 index 0000000000000..f0308134c2b87 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/kotlin/codestart.yml @@ -0,0 +1,3 @@ +--- +name: kotlin +type: language \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/scala/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/scala/codestart.yml new file mode 100644 index 0000000000000..80cbfab1babb2 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/language/scala/codestart.yml @@ -0,0 +1,3 @@ +--- +name: scala +type: language \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/base/.gitignore.r-qute b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/base/.gitignore.r-qute new file mode 100644 index 0000000000000..d4d7809fd7964 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/base/.gitignore.r-qute @@ -0,0 +1,31 @@ +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +{gitignore} diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/base/README.r-qute.md b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/base/README.r-qute.md new file mode 100644 index 0000000000000..30df131a7ba8a --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/base/README.r-qute.md @@ -0,0 +1,43 @@ +# {project.artifact-id} project + +This project uses Quarkus, the Supersonic Subatomic Java Framework. + +If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . + +## Running the application in dev mode + +You can run your application in dev mode that enables live coding using: +```shell script +{buildtool.cmd.dev} +``` + +## Packaging and running the application + +The application can be packaged using: +```shell script +{buildtool.cmd.package} +``` +It produces the `{project.artifact-id}-{project.version}-runner.jar` file in the `/{buildtool.build-dir}` directory. +Be aware that it’s not an _über-jar_ as the dependencies are copied into the `{buildtool.build-dir}/lib` directory. +If you want to build an _über-jar_, just add the `--uber-jar` option to the command line: +```shell script +{buildtool.cmd.package-uberjar} +``` + +The application is now runnable using `java -jar {buildtool.build-dir}/{project.artifact-id}-{project.version}-runner.jar`. + +## Creating a native executable + +You can create a native executable using: +```shell script +{buildtool.cmd.package-native} +``` + +Or, if you don't have GraalVM installed, you can run the native executable build in a container using: +```shell script +{buildtool.cmd.package-native-container} +``` + +You can then execute your native executable with: `./{buildtool.build-dir}/{project.artifact-id}-{project.version}-runner` + +If you want to learn more about building native executables, please consult {buildtool.guide}. diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/base/src/main/resources/application.w-config.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/base/src/main/resources/application.w-config.yml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/codestart.yml new file mode 100644 index 0000000000000..eb40383bbb782 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/project/quarkus/codestart.yml @@ -0,0 +1,10 @@ +--- +name: quarkus +type: project +fallback: true +spec: + base: + shared-data: + project: + group-id: org.acme + artifact-id: quarkus-project diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/.dockerignore.r-qute b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/.dockerignore.r-qute new file mode 100644 index 0000000000000..0060b04681bd0 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/.dockerignore.r-qute @@ -0,0 +1,4 @@ +* +!{buildtool.build-dir}/*-runner +!{buildtool.build-dir}/*-runner.jar +!{buildtool.build-dir}/lib/* \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.fast-jar b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.fast-jar new file mode 100644 index 0000000000000..fcfd215f023cc --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.fast-jar @@ -0,0 +1,56 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the docker image run: +# +# mvn package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/{project.artifact-id}-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/{project.artifact-id}-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/{project.artifact-id}-jvm +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 + +ARG JAVA_PACKAGE=java-11-openjdk-headless +ARG RUN_JAVA_VERSION=1.3.8 + +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' + +# Install java and the run-java script +# Also set up permissions for user `1001` +RUN microdnf install curl ca-certificates $\{JAVA_PACKAGE} \ + && microdnf update \ + && microdnf clean all \ + && mkdir /deployments \ + && chown 1001 /deployments \ + && chmod "g+rwX" /deployments \ + && chown 1001:root /deployments \ + && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/$\{RUN_JAVA_VERSION}/run-java-sh-$\{RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ + && chown 1001 /deployments/run-java.sh \ + && chmod 540 /deployments/run-java.sh \ + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security + +# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" + +COPY {buildtool.build-dir}/quarkus-app/lib/* /deployments/lib/ +COPY {buildtool.build-dir}/quarkus-app/quarkus-run.jar /deployments/app.jar +COPY {buildtool.build-dir}/quarkus-app/app/* /deployments/app/ +COPY {buildtool.build-dir}/quarkus-app/quarkus/* /deployments/quarkus/ + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT [ "/deployments/run-java.sh" ] diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.jvm b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.jvm new file mode 100644 index 0000000000000..1ad005cd38df3 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.jvm @@ -0,0 +1,54 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the docker image run: +# +# mvn package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/{project.artifact-id}-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/{project.artifact-id}-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5050 +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/{project.artifact-id}-jvm +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 + +ARG JAVA_PACKAGE=java-11-openjdk-headless +ARG RUN_JAVA_VERSION=1.3.8 + +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' + +# Install java and the run-java script +# Also set up permissions for user `1001` +RUN microdnf install curl ca-certificates $\{JAVA_PACKAGE} \ + && microdnf update \ + && microdnf clean all \ + && mkdir /deployments \ + && chown 1001 /deployments \ + && chmod "g+rwX" /deployments \ + && chown 1001:root /deployments \ + && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/$\{RUN_JAVA_VERSION}/run-java-sh-$\{RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ + && chown 1001 /deployments/run-java.sh \ + && chmod 540 /deployments/run-java.sh \ + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security + +# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" + +COPY {buildtool.build-dir}/lib/* /deployments/lib/ +COPY {buildtool.build-dir}/*-runner.jar /deployments/app.jar + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT [ "/deployments/run-java.sh" ] \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.native b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.native new file mode 100644 index 0000000000000..460d97b0ee86e --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/base/src/main/docker/Dockerfile.r-qute.native @@ -0,0 +1,24 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode +# +# Before building the docker image run: +# +# mvn package -Pnative -Dquarkus.native.container-build=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/{project.artifact-id} . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/{project.artifact-id} +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 +WORKDIR /work/ +COPY --chown=1001:root {buildtool.build-dir}/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/codestart.yml new file mode 100644 index 0000000000000..a44cb0c37183c --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/bundled-codestarts/tooling/dockerfiles/codestart.yml @@ -0,0 +1,4 @@ +--- +name: dockerfiles +type: tooling +preselected: true diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/base/README.w-append.r-qute.md b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/base/README.w-append.r-qute.md new file mode 100644 index 0000000000000..50a9a406aab0e --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/base/README.w-append.r-qute.md @@ -0,0 +1,41 @@ +# Quarkus demo: OptaPlanner AI with Hibernate ORM and RESTEasy + +This project contains a [Quarkus](https://quarkus.io/) application +with [OptaPlanner](https://www.optaplanner.org/)'s constraint solving Artificial Intelligence (AI) +integrated with a database and exposed through a REST API. + +This web application optimizes a school timetable for students and teachers. +It assigns `Lesson` instances to `Timeslot` and `Room` instances automatically +by using AI to adhere to hard and soft scheduling constraints, such as: + +* *Room conflict*: A room can have at most one lesson at the same time. +* *Teacher conflict*: A teacher can teach at most one lesson at the same time. +* *Student group conflict*: A student can attend at most one lesson at the same time. +* *Teacher room stability*: A teacher prefers to teach in a single room. +* *Teacher time efficiency*: A teacher prefers to teach sequential lessons and dislikes gaps between lessons. +* *Student group subject variety*: A student group dislikes sequential lessons on the same subject. + +## Some tips to run a native executable + +You can also create a native executable from this application without making any +source code changes. A native executable removes the dependency on the JVM: +everything needed to run the application on the target platform is included in +the executable, allowing the application to run with minimal resource overhead. + +Because the quarkus H2 extension does not support compiling the embedded database engine into native images, +you need to run the H2 server locally first: + + 1. Download the [H2 engine](http://www.h2database.com/html/download.html) (Platform-independent zip) + + 2. Unzip it. + + 3. Start H2 server with the option `-ifNotExists` (this is not recommended in production but saves you from creating the database manually) + + ```shell script + cd h2/bin && java -cp h2*.jar org.h2.tools.Server -ifNotExists + ``` + +The database connection in configured in the `{config.file-name}` file, +specifically with the `%prod.quarkus.datasource.*` properties. + + diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/codestart.yml new file mode 100644 index 0000000000000..e22c5a38c406b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/codestart.yml @@ -0,0 +1,20 @@ +--- +name: optaplanner-example +ref: optaplanner +type: example +spec: + base: + dependencies: + - io.quarkus:quarkus-optaplanner + - io.quarkus:quarkus-optaplanner-jackson + - io.quarkus:quarkus-resteasy + - io.quarkus:quarkus-resteasy-jackson + - io.quarkus:quarkus-hibernate-orm-panache + - io.quarkus:quarkus-jdbc-h2 + - org.webjars:bootstrap:4.3.1 + - org.webjars:jquery:3.4.1 + - org.webjars:font-awesome:5.11.2 + - org.webjars:momentjs:2.24.0 + test-dependencies: + - io.rest-assured:rest-assured + - io.quarkus:quarkus-test-h2 diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/bootstrap/DemoDataGenerator.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/bootstrap/DemoDataGenerator.java new file mode 100644 index 0000000000000..8c363d46ba3a0 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/bootstrap/DemoDataGenerator.java @@ -0,0 +1,194 @@ +package org.acme.optaplanner.bootstrap; + +import java.time.DayOfWeek; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.transaction.Transactional; + +import org.acme.optaplanner.domain.Lesson; +import org.acme.optaplanner.domain.Room; +import org.acme.optaplanner.domain.Timeslot; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.runtime.StartupEvent; + +@ApplicationScoped +public class DemoDataGenerator { + + @ConfigProperty(name = "timeTable.demoData", defaultValue = "SMALL") + DemoData demoData; + + @Transactional + public void generateDemoData(@Observes StartupEvent startupEvent) { + if (demoData == DemoData.NONE) { + return; + } + + List timeslotList = new ArrayList<>(10); + timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30))); + timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30))); + timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30))); + timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30))); + timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30))); + + timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(8, 30), LocalTime.of(9, 30))); + timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(9, 30), LocalTime.of(10, 30))); + timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(10, 30), LocalTime.of(11, 30))); + timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(13, 30), LocalTime.of(14, 30))); + timeslotList.add(new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(14, 30), LocalTime.of(15, 30))); + if (demoData == DemoData.LARGE) { + timeslotList.add(new Timeslot(DayOfWeek.WEDNESDAY, LocalTime.of(8, 30), LocalTime.of(9, 30))); + timeslotList.add(new Timeslot(DayOfWeek.WEDNESDAY, LocalTime.of(9, 30), LocalTime.of(10, 30))); + timeslotList.add(new Timeslot(DayOfWeek.WEDNESDAY, LocalTime.of(10, 30), LocalTime.of(11, 30))); + timeslotList.add(new Timeslot(DayOfWeek.WEDNESDAY, LocalTime.of(13, 30), LocalTime.of(14, 30))); + timeslotList.add(new Timeslot(DayOfWeek.WEDNESDAY, LocalTime.of(14, 30), LocalTime.of(15, 30))); + timeslotList.add(new Timeslot(DayOfWeek.THURSDAY, LocalTime.of(8, 30), LocalTime.of(9, 30))); + timeslotList.add(new Timeslot(DayOfWeek.THURSDAY, LocalTime.of(9, 30), LocalTime.of(10, 30))); + timeslotList.add(new Timeslot(DayOfWeek.THURSDAY, LocalTime.of(10, 30), LocalTime.of(11, 30))); + timeslotList.add(new Timeslot(DayOfWeek.THURSDAY, LocalTime.of(13, 30), LocalTime.of(14, 30))); + timeslotList.add(new Timeslot(DayOfWeek.THURSDAY, LocalTime.of(14, 30), LocalTime.of(15, 30))); + timeslotList.add(new Timeslot(DayOfWeek.FRIDAY, LocalTime.of(8, 30), LocalTime.of(9, 30))); + timeslotList.add(new Timeslot(DayOfWeek.FRIDAY, LocalTime.of(9, 30), LocalTime.of(10, 30))); + timeslotList.add(new Timeslot(DayOfWeek.FRIDAY, LocalTime.of(10, 30), LocalTime.of(11, 30))); + timeslotList.add(new Timeslot(DayOfWeek.FRIDAY, LocalTime.of(13, 30), LocalTime.of(14, 30))); + timeslotList.add(new Timeslot(DayOfWeek.FRIDAY, LocalTime.of(14, 30), LocalTime.of(15, 30))); + } + Timeslot.persist(timeslotList); + + List roomList = new ArrayList<>(3); + roomList.add(new Room("Room A")); + roomList.add(new Room("Room B")); + roomList.add(new Room("Room C")); + if (demoData == DemoData.LARGE) { + roomList.add(new Room("Room D")); + roomList.add(new Room("Room E")); + roomList.add(new Room("Room F")); + } + Room.persist(roomList); + + List lessonList = new ArrayList<>(); + lessonList.add(new Lesson("Math", "A. Turing", "9th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "9th grade")); + lessonList.add(new Lesson("Physics", "M. Curie", "9th grade")); + lessonList.add(new Lesson("Chemistry", "M. Curie", "9th grade")); + lessonList.add(new Lesson("Biology", "C. Darwin", "9th grade")); + lessonList.add(new Lesson("History", "I. Jones", "9th grade")); + lessonList.add(new Lesson("English", "I. Jones", "9th grade")); + lessonList.add(new Lesson("English", "I. Jones", "9th grade")); + lessonList.add(new Lesson("Spanish", "P. Cruz", "9th grade")); + lessonList.add(new Lesson("Spanish", "P. Cruz", "9th grade")); + if (demoData == DemoData.LARGE) { + lessonList.add(new Lesson("Math", "A. Turing", "9th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "9th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "9th grade")); + lessonList.add(new Lesson("ICT", "A. Turing", "9th grade")); + lessonList.add(new Lesson("Physics", "M. Curie", "9th grade")); + lessonList.add(new Lesson("Geography", "C. Darwin", "9th grade")); + lessonList.add(new Lesson("Geology", "C. Darwin", "9th grade")); + lessonList.add(new Lesson("History", "I. Jones", "9th grade")); + lessonList.add(new Lesson("English", "I. Jones", "9th grade")); + lessonList.add(new Lesson("Drama", "I. Jones", "9th grade")); + lessonList.add(new Lesson("Art", "S. Dali", "9th grade")); + lessonList.add(new Lesson("Art", "S. Dali", "9th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "9th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "9th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "9th grade")); + } + + lessonList.add(new Lesson("Math", "A. Turing", "10th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "10th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "10th grade")); + lessonList.add(new Lesson("Physics", "M. Curie", "10th grade")); + lessonList.add(new Lesson("Chemistry", "M. Curie", "10th grade")); + lessonList.add(new Lesson("French", "M. Curie", "10th grade")); + lessonList.add(new Lesson("Geography", "C. Darwin", "10th grade")); + lessonList.add(new Lesson("History", "I. Jones", "10th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "10th grade")); + lessonList.add(new Lesson("Spanish", "P. Cruz", "10th grade")); + if (demoData == DemoData.LARGE) { + lessonList.add(new Lesson("Math", "A. Turing", "10th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "10th grade")); + lessonList.add(new Lesson("ICT", "A. Turing", "10th grade")); + lessonList.add(new Lesson("Physics", "M. Curie", "10th grade")); + lessonList.add(new Lesson("Biology", "C. Darwin", "10th grade")); + lessonList.add(new Lesson("Geology", "C. Darwin", "10th grade")); + lessonList.add(new Lesson("History", "I. Jones", "10th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "10th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "10th grade")); + lessonList.add(new Lesson("Drama", "I. Jones", "10th grade")); + lessonList.add(new Lesson("Art", "S. Dali", "10th grade")); + lessonList.add(new Lesson("Art", "S. Dali", "10th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "10th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "10th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "10th grade")); + + lessonList.add(new Lesson("Math", "A. Turing", "11th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "11th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "11th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "11th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "11th grade")); + lessonList.add(new Lesson("ICT", "A. Turing", "11th grade")); + lessonList.add(new Lesson("Physics", "M. Curie", "11th grade")); + lessonList.add(new Lesson("Chemistry", "M. Curie", "11th grade")); + lessonList.add(new Lesson("French", "M. Curie", "11th grade")); + lessonList.add(new Lesson("Physics", "M. Curie", "11th grade")); + lessonList.add(new Lesson("Geography", "C. Darwin", "11th grade")); + lessonList.add(new Lesson("Biology", "C. Darwin", "11th grade")); + lessonList.add(new Lesson("Geology", "C. Darwin", "11th grade")); + lessonList.add(new Lesson("History", "I. Jones", "11th grade")); + lessonList.add(new Lesson("History", "I. Jones", "11th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "11th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "11th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "11th grade")); + lessonList.add(new Lesson("Spanish", "P. Cruz", "11th grade")); + lessonList.add(new Lesson("Drama", "P. Cruz", "11th grade")); + lessonList.add(new Lesson("Art", "S. Dali", "11th grade")); + lessonList.add(new Lesson("Art", "S. Dali", "11th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "11th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "11th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "11th grade")); + + lessonList.add(new Lesson("Math", "A. Turing", "12th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "12th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "12th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "12th grade")); + lessonList.add(new Lesson("Math", "A. Turing", "12th grade")); + lessonList.add(new Lesson("ICT", "A. Turing", "12th grade")); + lessonList.add(new Lesson("Physics", "M. Curie", "12th grade")); + lessonList.add(new Lesson("Chemistry", "M. Curie", "12th grade")); + lessonList.add(new Lesson("French", "M. Curie", "12th grade")); + lessonList.add(new Lesson("Physics", "M. Curie", "12th grade")); + lessonList.add(new Lesson("Geography", "C. Darwin", "12th grade")); + lessonList.add(new Lesson("Biology", "C. Darwin", "12th grade")); + lessonList.add(new Lesson("Geology", "C. Darwin", "12th grade")); + lessonList.add(new Lesson("History", "I. Jones", "12th grade")); + lessonList.add(new Lesson("History", "I. Jones", "12th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "12th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "12th grade")); + lessonList.add(new Lesson("English", "P. Cruz", "12th grade")); + lessonList.add(new Lesson("Spanish", "P. Cruz", "12th grade")); + lessonList.add(new Lesson("Drama", "P. Cruz", "12th grade")); + lessonList.add(new Lesson("Art", "S. Dali", "12th grade")); + lessonList.add(new Lesson("Art", "S. Dali", "12th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "12th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "12th grade")); + lessonList.add(new Lesson("Physical education", "C. Lewis", "12th grade")); + } + + Lesson lesson = lessonList.get(0); + lesson.setTimeslot(timeslotList.get(0)); + lesson.setRoom(roomList.get(0)); + Lesson.persist(lessonList); + } + + public enum DemoData { + NONE, + SMALL, + LARGE + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Lesson.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Lesson.java new file mode 100644 index 0000000000000..4fe1ae1c6b61f --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Lesson.java @@ -0,0 +1,87 @@ +package org.acme.optaplanner.domain; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import org.optaplanner.core.api.domain.entity.PlanningEntity; +import org.optaplanner.core.api.domain.lookup.PlanningId; +import org.optaplanner.core.api.domain.variable.PlanningVariable; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; + +@PlanningEntity +@Entity +public class Lesson extends PanacheEntityBase { + + @PlanningId + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @NotNull + private Long id; + + @NotBlank + private String subject; + @NotBlank + private String teacher; + @NotBlank + private String studentGroup; + + @PlanningVariable(valueRangeProviderRefs = "timeslotRange") + @ManyToOne + private Timeslot timeslot; + @PlanningVariable(valueRangeProviderRefs = "roomRange") + @ManyToOne + private Room room; + + public Lesson() { + } + + public Lesson(String subject, String teacher, String studentGroup) { + this.subject = subject.trim(); + this.teacher = teacher.trim(); + this.studentGroup = studentGroup.trim(); + } + + public Long getId() { + return id; + } + + public String getSubject() { + return subject; + } + + public String getTeacher() { + return teacher; + } + + public String getStudentGroup() { + return studentGroup; + } + + public Timeslot getTimeslot() { + return timeslot; + } + + public void setTimeslot(Timeslot timeslot) { + this.timeslot = timeslot; + } + + public Room getRoom() { + return room; + } + + public void setRoom(Room room) { + this.room = room; + } + + @Override + public String toString() { + return subject + "(" + id + ")"; + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Room.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Room.java new file mode 100644 index 0000000000000..0a4ba4625c297 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Room.java @@ -0,0 +1,46 @@ +package org.acme.optaplanner.domain; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import org.optaplanner.core.api.domain.lookup.PlanningId; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; + +@Entity +public class Room extends PanacheEntityBase { + + @PlanningId + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @NotNull + private Long id; + + @NotBlank + private String name; + + public Room() { + } + + public Room(String name) { + this.name = name.trim(); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/TimeTable.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/TimeTable.java new file mode 100644 index 0000000000000..52e2eba8991e3 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/TimeTable.java @@ -0,0 +1,64 @@ +package org.acme.optaplanner.domain; + +import java.util.List; + +import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty; +import org.optaplanner.core.api.domain.solution.PlanningScore; +import org.optaplanner.core.api.domain.solution.PlanningSolution; +import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty; +import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider; +import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; +import org.optaplanner.core.api.solver.SolverStatus; + +@PlanningSolution +public class TimeTable { + + @ProblemFactCollectionProperty + @ValueRangeProvider(id = "timeslotRange") + private List timeslotList; + @ProblemFactCollectionProperty + @ValueRangeProvider(id = "roomRange") + private List roomList; + @PlanningEntityCollectionProperty + private List lessonList; + + @PlanningScore + private HardSoftScore score; + + // Ignored by OptaPlanner, used by the UI to display solve or stop solving button + private SolverStatus solverStatus; + + public TimeTable() { + } + + public TimeTable(List timeslotList, List roomList, List lessonList) { + this.timeslotList = timeslotList; + this.roomList = roomList; + this.lessonList = lessonList; + } + + public List getTimeslotList() { + return timeslotList; + } + + public List getRoomList() { + return roomList; + } + + public List getLessonList() { + return lessonList; + } + + public HardSoftScore getScore() { + return score; + } + + public SolverStatus getSolverStatus() { + return solverStatus; + } + + public void setSolverStatus(SolverStatus solverStatus) { + this.solverStatus = solverStatus; + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Timeslot.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Timeslot.java new file mode 100644 index 0000000000000..8f4ed811cb1a6 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/domain/Timeslot.java @@ -0,0 +1,62 @@ +package org.acme.optaplanner.domain; + +import java.time.DayOfWeek; +import java.time.LocalTime; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.validation.constraints.NotNull; + +import org.optaplanner.core.api.domain.lookup.PlanningId; + +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; + +@Entity +public class Timeslot extends PanacheEntityBase { + + @PlanningId + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @NotNull + private Long id; + + @NotNull + private DayOfWeek dayOfWeek; + @NotNull + private LocalTime startTime; + @NotNull + private LocalTime endTime; + + public Timeslot() { + } + + public Timeslot(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) { + this.dayOfWeek = dayOfWeek; + this.startTime = startTime; + this.endTime = endTime; + } + + public Long getId() { + return id; + } + + public DayOfWeek getDayOfWeek() { + return dayOfWeek; + } + + public LocalTime getStartTime() { + return startTime; + } + + public LocalTime getEndTime() { + return endTime; + } + + @Override + public String toString() { + return dayOfWeek + " " + startTime; + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/LessonResource.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/LessonResource.java new file mode 100644 index 0000000000000..da9529e879f2b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/LessonResource.java @@ -0,0 +1,48 @@ +package org.acme.optaplanner.rest; + +import java.util.List; + +import javax.transaction.Transactional; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.acme.optaplanner.domain.Lesson; + +import io.quarkus.panache.common.Sort; + +@Path("/lessons") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Transactional +public class LessonResource { + + @GET + public List getAllLessons() { + return Lesson.listAll(Sort.by("subject").and("teacher").and("studentGroup").and("id")); + } + + @POST + public Response add(Lesson lesson) { + Lesson.persist(lesson); + return Response.accepted(lesson).build(); + } + + @DELETE + @Path("{lessonId}") + public Response delete(@PathParam("lessonId") Long lessonId) { + Lesson lesson = Lesson.findById(lessonId); + if (lesson == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + lesson.delete(); + return Response.status(Response.Status.OK).build(); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/RoomResource.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/RoomResource.java new file mode 100644 index 0000000000000..f3bc9bb211452 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/RoomResource.java @@ -0,0 +1,49 @@ +package org.acme.optaplanner.rest; + +import java.util.List; + +import javax.transaction.Transactional; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.acme.optaplanner.domain.Room; + +import io.quarkus.panache.common.Sort; + +@Path("/rooms") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Transactional +public class RoomResource { + + @GET + public List getAllRooms() { + return Room.listAll(Sort.by("name").and("id")); + } + + // To try: curl -d '{"name":"Room Z"}' -H "Content-Type: application/json" -X POST http://localhost:8080/rooms + @POST + public Response add(Room room) { + Room.persist(room); + return Response.accepted(room).build(); + } + + @DELETE + @Path("{roomId}") + public Response delete(@PathParam("roomId") Long roomId) { + Room room = Room.findById(roomId); + if (room == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + room.delete(); + return Response.status(Response.Status.OK).build(); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/TimeTableResource.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/TimeTableResource.java new file mode 100644 index 0000000000000..fdf24315712c4 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/TimeTableResource.java @@ -0,0 +1,87 @@ +package org.acme.optaplanner.rest; + +import javax.inject.Inject; +import javax.transaction.Transactional; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.acme.optaplanner.domain.Lesson; +import org.acme.optaplanner.domain.Room; +import org.acme.optaplanner.domain.TimeTable; +import org.acme.optaplanner.domain.Timeslot; +import org.optaplanner.core.api.score.ScoreManager; +import org.optaplanner.core.api.solver.SolverManager; +import org.optaplanner.core.api.solver.SolverStatus; + +import io.quarkus.panache.common.Sort; + +@Path("/timeTable") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class TimeTableResource { + + public static final Long SINGLETON_TIME_TABLE_ID = 1L; + + @Inject + SolverManager solverManager; + @Inject + ScoreManager scoreManager; + + // To try, open http://localhost:8080/timeTable + @GET + public TimeTable getTimeTable() { + // Get the solver status before loading the solution + // to avoid the race condition that the solver terminates between them + SolverStatus solverStatus = getSolverStatus(); + TimeTable solution = findById(SINGLETON_TIME_TABLE_ID); + scoreManager.updateScore(solution); // Sets the score + solution.setSolverStatus(solverStatus); + return solution; + } + + @POST + @Path("/solve") + public void solve() { + solverManager.solveAndListen(SINGLETON_TIME_TABLE_ID, + this::findById, + this::save); + } + + public SolverStatus getSolverStatus() { + return solverManager.getSolverStatus(SINGLETON_TIME_TABLE_ID); + } + + @POST + @Path("/stopSolving") + public void stopSolving() { + solverManager.terminateEarly(SINGLETON_TIME_TABLE_ID); + } + + @Transactional + protected TimeTable findById(Long id) { + if (!SINGLETON_TIME_TABLE_ID.equals(id)) { + throw new IllegalStateException("There is no timeTable with id (" + id + ")."); + } + // Occurs in a single transaction, so each initialized lesson references the same timeslot/room instance + // that is contained by the timeTable's timeslotList/roomList. + return new TimeTable( + Timeslot.listAll(Sort.by("dayOfWeek").and("startTime").and("endTime").and("id")), + Room.listAll(Sort.by("name").and("id")), + Lesson.listAll(Sort.by("subject").and("teacher").and("studentGroup").and("id"))); + } + + @Transactional + protected void save(TimeTable timeTable) { + for (Lesson lesson : timeTable.getLessonList()) { + // TODO this is awfully naive: optimistic locking causes issues if called by the SolverManager + Lesson attachedLesson = Lesson.findById(lesson.getId()); + attachedLesson.setTimeslot(lesson.getTimeslot()); + attachedLesson.setRoom(lesson.getRoom()); + } + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/TimeslotResource.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/TimeslotResource.java new file mode 100644 index 0000000000000..1e28350800dad --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/rest/TimeslotResource.java @@ -0,0 +1,48 @@ +package org.acme.optaplanner.rest; + +import java.util.List; + +import javax.transaction.Transactional; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.acme.optaplanner.domain.Timeslot; + +import io.quarkus.panache.common.Sort; + +@Path("/timeslots") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Transactional +public class TimeslotResource { + + @GET + public List getAllTimeslots() { + return Timeslot.listAll(Sort.by("dayOfWeek").and("startTime").and("endTime").and("id")); + } + + @POST + public Response add(Timeslot timeslot) { + Timeslot.persist(timeslot); + return Response.accepted(timeslot).build(); + } + + @DELETE + @Path("{timeslotId}") + public Response delete(@PathParam("timeslotId") Long timeslotId) { + Timeslot timeslot = Timeslot.findById(timeslotId); + if (timeslot == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + timeslot.delete(); + return Response.status(Response.Status.OK).build(); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/solver/TimeTableConstraintProvider.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/solver/TimeTableConstraintProvider.java new file mode 100644 index 0000000000000..4e27c1a952620 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/java/org/acme/optaplanner/solver/TimeTableConstraintProvider.java @@ -0,0 +1,98 @@ +package org.acme.optaplanner.solver; + +import java.time.Duration; + +import org.acme.optaplanner.domain.Lesson; +import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; +import org.optaplanner.core.api.score.stream.Constraint; +import org.optaplanner.core.api.score.stream.ConstraintFactory; +import org.optaplanner.core.api.score.stream.ConstraintProvider; +import org.optaplanner.core.api.score.stream.Joiners; + +public class TimeTableConstraintProvider implements ConstraintProvider { + + @Override + public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { + return new Constraint[] { + // Hard constraints + roomConflict(constraintFactory), + teacherConflict(constraintFactory), + studentGroupConflict(constraintFactory), + // Soft constraints + teacherRoomStability(constraintFactory), + teacherTimeEfficiency(constraintFactory), + studentGroupSubjectVariety(constraintFactory) + }; + } + + private Constraint roomConflict(ConstraintFactory constraintFactory) { + // A room can accommodate at most one lesson at the same time. + return constraintFactory + // Select each pair of 2 different lessons ... + .fromUniquePair(Lesson.class, + // ... in the same timeslot ... + Joiners.equal(Lesson::getTimeslot), + // ... in the same room ... + Joiners.equal(Lesson::getRoom)) + // ... and penalize each pair with a hard weight. + .penalize("Room conflict", HardSoftScore.ONE_HARD); + } + + private Constraint teacherConflict(ConstraintFactory constraintFactory) { + // A teacher can teach at most one lesson at the same time. + return constraintFactory + .fromUniquePair(Lesson.class, + Joiners.equal(Lesson::getTimeslot), + Joiners.equal(Lesson::getTeacher)) + .penalize("Teacher conflict", HardSoftScore.ONE_HARD); + } + + private Constraint studentGroupConflict(ConstraintFactory constraintFactory) { + // A student can attend at most one lesson at the same time. + return constraintFactory + .fromUniquePair(Lesson.class, + Joiners.equal(Lesson::getTimeslot), + Joiners.equal(Lesson::getStudentGroup)) + .penalize("Student group conflict", HardSoftScore.ONE_HARD); + } + + private Constraint teacherRoomStability(ConstraintFactory constraintFactory) { + // A teacher prefers to teach in a single room. + return constraintFactory + .fromUniquePair(Lesson.class, + Joiners.equal(Lesson::getTeacher)) + .filter((lesson1, lesson2) -> lesson1.getRoom() != lesson2.getRoom()) + .penalize("Teacher room stability", HardSoftScore.ONE_SOFT); + } + + private Constraint teacherTimeEfficiency(ConstraintFactory constraintFactory) { + // A teacher prefers to teach sequential lessons and dislikes gaps between lessons. + return constraintFactory + .from(Lesson.class) + .join(Lesson.class, Joiners.equal(Lesson::getTeacher), + Joiners.equal((lesson) -> lesson.getTimeslot().getDayOfWeek())) + .filter((lesson1, lesson2) -> { + Duration between = Duration.between(lesson1.getTimeslot().getEndTime(), + lesson2.getTimeslot().getStartTime()); + return !between.isNegative() && between.compareTo(Duration.ofMinutes(30)) <= 0; + }) + .reward("Teacher time efficiency", HardSoftScore.ONE_SOFT); + } + + private Constraint studentGroupSubjectVariety(ConstraintFactory constraintFactory) { + // A student group dislikes sequential lessons on the same subject. + return constraintFactory + .from(Lesson.class) + .join(Lesson.class, + Joiners.equal(Lesson::getSubject), + Joiners.equal(Lesson::getStudentGroup), + Joiners.equal((lesson) -> lesson.getTimeslot().getDayOfWeek())) + .filter((lesson1, lesson2) -> { + Duration between = Duration.between(lesson1.getTimeslot().getEndTime(), + lesson2.getTimeslot().getStartTime()); + return !between.isNegative() && between.compareTo(Duration.ofMinutes(30)) <= 0; + }) + .penalize("Student group subject variety", HardSoftScore.ONE_SOFT); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/app.js b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/app.js new file mode 100644 index 0000000000000..3f351ec3cc4ca --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/app.js @@ -0,0 +1,349 @@ +var autoRefreshCount = 0; +var autoRefreshIntervalId = null; + +function refreshTimeTable() { + $.getJSON("/timeTable", function (timeTable) { + refreshSolvingButtons(timeTable.solverStatus != null && timeTable.solverStatus !== "NOT_SOLVING"); + $("#score").text("Score: "+ (timeTable.score == null ? "?" : timeTable.score)); + + const timeTableByRoom = $("#timeTableByRoom"); + timeTableByRoom.children().remove(); + const timeTableByTeacher = $("#timeTableByTeacher"); + timeTableByTeacher.children().remove(); + const timeTableByStudentGroup = $("#timeTableByStudentGroup"); + timeTableByStudentGroup.children().remove(); + const unassignedLessons = $("#unassignedLessons"); + unassignedLessons.children().remove(); + + const theadByRoom = $("").appendTo(timeTableByRoom); + const headerRowByRoom = $("").appendTo(theadByRoom); + headerRowByRoom.append($("Timeslot")); + $.each(timeTable.roomList, (index, room) => { + headerRowByRoom + .append($("") + .append($("").text(room.name)) + .append($(` + `)) + .append($(`
`) + .append($(`

`).text(title)) + .append($(`

`)
+                            .append($(``).text(serverErrorMessage))
+                    )
+            );
+    $("#notificationPanel").append(notification);
+    notification.toast({delay: 30000});
+    notification.toast('show');
+}
+
+$(document).ready( function() {
+    $.ajaxSetup({
+        headers: {
+            'Content-Type': 'application/json',
+            'Accept': 'application/json'
+        }
+    });
+    // Extend jQuery to support $.put() and $.delete()
+    jQuery.each( [ "put", "delete" ], function( i, method ) {
+        jQuery[method] = function (url, data, callback, type) {
+            if (jQuery.isFunction(data)) {
+                type = type || callback;
+                callback = data;
+                data = undefined;
+            }
+            return jQuery.ajax({
+                url: url,
+                type: method,
+                dataType: type,
+                data: data,
+                success: callback
+            });
+        };
+    });
+
+
+    $("#refreshButton").click(function() {
+        refreshTimeTable();
+    });
+    $("#solveButton").click(function() {
+        solve();
+    });
+    $("#stopSolvingButton").click(function() {
+        stopSolving();
+    });
+    $("#addLessonSubmitButton").click(function() {
+        addLesson();
+    });
+    $("#addTimeslotSubmitButton").click(function() {
+        addTimeslot();
+    });
+    $("#addRoomSubmitButton").click(function() {
+        addRoom();
+    });
+
+    refreshTimeTable();
+});
+
+// ****************************************************************************
+// TangoColorFactory
+// ****************************************************************************
+
+const SEQUENCE_1 = [0x8AE234, 0xFCE94F, 0x729FCF, 0xE9B96E, 0xAD7FA8];
+const SEQUENCE_2 = [0x73D216, 0xEDD400, 0x3465A4, 0xC17D11, 0x75507B];
+
+var colorMap = new Map;
+var nextColorCount = 0;
+
+function pickColor(object) {
+    let color = colorMap[object];
+    if (color !== undefined) {
+        return color;
+    }
+    color = nextColor();
+    colorMap[object] = color;
+    return color;
+}
+
+function nextColor() {
+    let color;
+    let colorIndex = nextColorCount % SEQUENCE_1.length;
+    let shadeIndex = Math.floor(nextColorCount / SEQUENCE_1.length);
+    if (shadeIndex === 0) {
+        color = SEQUENCE_1[colorIndex];
+    } else if (shadeIndex === 1) {
+        color = SEQUENCE_2[colorIndex];
+    } else {
+        shadeIndex -= 3;
+        let floorColor = SEQUENCE_2[colorIndex];
+        let ceilColor = SEQUENCE_1[colorIndex];
+        let base = Math.floor((shadeIndex / 2) + 1);
+        let divisor = 2;
+        while (base >= divisor) {
+            divisor *= 2;
+        }
+        base = (base * 2) - divisor + 1;
+        let shadePercentage = base / divisor;
+        color = buildPercentageColor(floorColor, ceilColor, shadePercentage);
+    }
+    nextColorCount++;
+    return "#" + color.toString(16);
+}
+
+function buildPercentageColor(floorColor, ceilColor, shadePercentage) {
+    let red = (floorColor & 0xFF0000) + Math.floor(shadePercentage * ((ceilColor & 0xFF0000) - (floorColor & 0xFF0000))) & 0xFF0000;
+    let green = (floorColor & 0x00FF00) + Math.floor(shadePercentage * ((ceilColor & 0x00FF00) - (floorColor & 0x00FF00))) & 0x00FF00;
+    let blue = (floorColor & 0x0000FF) + Math.floor(shadePercentage * ((ceilColor & 0x0000FF) - (floorColor & 0x0000FF))) & 0x0000FF;
+    return red | green | blue;
+}
diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/index.html b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/index.html
new file mode 100644
index 0000000000000..3578efe0c8b95
--- /dev/null
+++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/index.html
@@ -0,0 +1,179 @@
+
+
+    
+    
+    OptaPlanner Quarkus example
+    
+    
+
+
+
+ +
+
+
+

High school time table solver

+

Generate the optimal schedule for your teachers and students.

+ +
+ + + + + Score: ? + +
+ +
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + + +
+ +

Unassigned lessons

+
+
+ + + + + + + + + + + + diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/optaPlannerLogo200px.png b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/optaPlannerLogo200px.png new file mode 100644 index 0000000000000..63abac7a53b51 Binary files /dev/null and b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/META-INF/resources/optaPlannerLogo200px.png differ diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/application.smart-config.yml b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/application.smart-config.yml new file mode 100644 index 0000000000000..a0b90b7250d10 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/main/resources/application.smart-config.yml @@ -0,0 +1,29 @@ +#timeTable: +# demoData: LARGE +quarkus: + datasource: + db-kind: h2 + jdbc: + url: jdbc:h2:mem:school-timetabling + hibernate-orm: + database: + generation: drop-and-create + optaplanner: + solver: + termination: + spent-limit: 30s +"%test": + quarkus: + datasource: + jdbc: + url: jdbc:h2:tcp://localhost/mem:school-timetabling + optaplanner: + solver: + termination: + spent-limit: 1h + best-score-limit: 0hard/*soft +"%prod": + quarkus: + datasource: + jdbc: + url: jdbc:h2:tcp://localhost/mem:school-timetabling diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/TestResources.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/TestResources.java new file mode 100644 index 0000000000000..7cd28aa78e69c --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/TestResources.java @@ -0,0 +1,9 @@ +package org.acme.optaplanner; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TestResources { + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/LessonResourceIT.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/LessonResourceIT.java new file mode 100644 index 0000000000000..4dce1e7ae7f8c --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/LessonResourceIT.java @@ -0,0 +1,8 @@ +package org.acme.optaplanner.rest; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class LessonResourceIT extends LessonResourceTest { + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/LessonResourceTest.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/LessonResourceTest.java new file mode 100644 index 0000000000000..ac0e6d4762516 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/LessonResourceTest.java @@ -0,0 +1,50 @@ +package org.acme.optaplanner.rest; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.List; + +import org.acme.optaplanner.domain.Lesson; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +public class LessonResourceTest { + + @Test + public void getAll() { + List lessonList = given() + .when().get("/lessons") + .then() + .statusCode(200) + .extract().body().jsonPath().getList(".", Lesson.class); + assertFalse(lessonList.isEmpty()); + Lesson firstLesson = lessonList.get(0); + assertEquals("Biology", firstLesson.getSubject()); + assertEquals("C. Darwin", firstLesson.getTeacher()); + assertEquals("9th grade", firstLesson.getStudentGroup()); + } + + @Test + void addAndRemove() { + Lesson lesson = given() + .when() + .contentType(ContentType.JSON) + .body(new Lesson("Test subject", "Test teacher", "test studentGroup")) + .post("/lessons") + .then() + .statusCode(202) + .extract().as(Lesson.class); + + given() + .when() + .delete("/lessons/{id}", lesson.getId()) + .then() + .statusCode(200); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/RoomResourceIT.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/RoomResourceIT.java new file mode 100644 index 0000000000000..cfe6e4817659c --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/RoomResourceIT.java @@ -0,0 +1,8 @@ +package org.acme.optaplanner.rest; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class RoomResourceIT extends RoomResourceTest { + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/RoomResourceTest.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/RoomResourceTest.java new file mode 100644 index 0000000000000..b8d814f453d01 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/RoomResourceTest.java @@ -0,0 +1,48 @@ +package org.acme.optaplanner.rest; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.List; + +import org.acme.optaplanner.domain.Room; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +public class RoomResourceTest { + + @Test + public void getAll() { + List roomList = given() + .when().get("/rooms") + .then() + .statusCode(200) + .extract().body().jsonPath().getList(".", Room.class); + assertFalse(roomList.isEmpty()); + Room firstRoom = roomList.get(0); + assertEquals("Room A", firstRoom.getName()); + } + + @Test + void addAndRemove() { + Room room = given() + .when() + .contentType(ContentType.JSON) + .body(new Room("Test room")) + .post("/rooms") + .then() + .statusCode(202) + .extract().as(Room.class); + + given() + .when() + .delete("/rooms/{id}", room.getId()) + .then() + .statusCode(200); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeTableResourceTest.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeTableResourceTest.java new file mode 100644 index 0000000000000..5ca5a5b63c7f3 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeTableResourceTest.java @@ -0,0 +1,42 @@ +package org.acme.optaplanner.rest; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.inject.Inject; + +import org.acme.optaplanner.domain.Lesson; +import org.acme.optaplanner.domain.TimeTable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.optaplanner.core.api.solver.SolverStatus; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class TimeTableResourceTest { + + @Inject + TimeTableResource timeTableResource; + + @Test + @Timeout(600_000) + public void solveDemoDataUntilFeasible() throws InterruptedException { + timeTableResource.solve(); + TimeTable timeTable = timeTableResource.getTimeTable(); + while (timeTable.getSolverStatus() != SolverStatus.NOT_SOLVING) { + // Quick polling (not a Test Thread Sleep anti-pattern) + // Test is still fast on fast machines and doesn't randomly fail on slow machines. + Thread.sleep(20L); + timeTable = timeTableResource.getTimeTable(); + } + assertFalse(timeTable.getLessonList().isEmpty()); + for (Lesson lesson : timeTable.getLessonList()) { + assertNotNull(lesson.getTimeslot()); + assertNotNull(lesson.getRoom()); + } + assertTrue(timeTable.getScore().isFeasible()); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeslotResourceIT.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeslotResourceIT.java new file mode 100644 index 0000000000000..26f56dd2c184d --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeslotResourceIT.java @@ -0,0 +1,8 @@ +package org.acme.optaplanner.rest; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class TimeslotResourceIT extends TimeslotResourceTest { + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeslotResourceTest.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeslotResourceTest.java new file mode 100644 index 0000000000000..3ca4644599fed --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/rest/TimeslotResourceTest.java @@ -0,0 +1,52 @@ +package org.acme.optaplanner.rest; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.time.DayOfWeek; +import java.time.LocalTime; +import java.util.List; + +import org.acme.optaplanner.domain.Timeslot; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +public class TimeslotResourceTest { + + @Test + public void getAll() { + List timeslotList = given() + .when().get("/timeslots") + .then() + .statusCode(200) + .extract().body().jsonPath().getList(".", Timeslot.class); + assertFalse(timeslotList.isEmpty()); + Timeslot firstTimeslot = timeslotList.get(0); + assertEquals(DayOfWeek.MONDAY, firstTimeslot.getDayOfWeek()); + assertEquals(LocalTime.of(8, 30), firstTimeslot.getStartTime()); + assertEquals(LocalTime.of(9, 30), firstTimeslot.getEndTime()); + } + + @Test + void addAndRemove() { + Timeslot timeslot = given() + .when() + .contentType(ContentType.JSON) + .body(new Timeslot(DayOfWeek.SUNDAY, LocalTime.of(20, 0), LocalTime.of(21, 0))) + .post("/timeslots") + .then() + .statusCode(202) + .extract().as(Timeslot.class); + + given() + .when() + .delete("/timeslots/{id}", timeslot.getId()) + .then() + .statusCode(200); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/ui/WebjarLoadingIT.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/ui/WebjarLoadingIT.java new file mode 100644 index 0000000000000..18ea69be7ccdd --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/ui/WebjarLoadingIT.java @@ -0,0 +1,8 @@ +package org.acme.optaplanner.ui; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class WebjarLoadingIT extends WebjarLoadingTest { + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/ui/WebjarLoadingTest.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/ui/WebjarLoadingTest.java new file mode 100644 index 0000000000000..8be1e3ecfbe2d --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/optaplanner-example/java/src/test/java/org/acme/optaplanner/ui/WebjarLoadingTest.java @@ -0,0 +1,20 @@ +package org.acme.optaplanner.ui; + +import static io.restassured.RestAssured.given; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class WebjarLoadingTest { + + @Test + public void getBootstrapCss() { + given() + .when().get("webjars/bootstrap/4.3.1/css/bootstrap.min.css") + .then() + .statusCode(200); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/base/README.append.md b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/base/README.append.md new file mode 100644 index 0000000000000..014f67c64f84b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/base/README.append.md @@ -0,0 +1,5 @@ +# Qute example + +Quarkus guide: https://quarkus.io/guides/qute + +Reference guide: https://quarkus.io/guides/qute-reference \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/codestart.yml new file mode 100644 index 0000000000000..02a3f2f78cab6 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/codestart.yml @@ -0,0 +1,10 @@ +--- +name: qute-example +ref: qute +type: example +spec: + base: + dependencies: + - io.quarkus:quarkus-resteasy-qute + test-dependencies: + - io.rest-assured:rest-assured diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/HelloResource.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/HelloResource.java new file mode 100644 index 0000000000000..f02162495d5db --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/HelloResource.java @@ -0,0 +1,25 @@ +package org.acme.qute; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateInstance; + +@Path("/hello") +public class HelloResource { + + @Inject + Template hello; + + @GET + @Produces(MediaType.TEXT_HTML) + public TemplateInstance get(@QueryParam("name") String name) { + return hello.data("name", name); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/Item.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/Item.java new file mode 100644 index 0000000000000..6a3dc8c942ca6 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/Item.java @@ -0,0 +1,15 @@ +package org.acme.qute; + +import java.math.BigDecimal; + +public class Item { + + public final BigDecimal price; + public final String name; + + public Item(BigDecimal price, String name) { + this.price = price; + this.name = name; + } + +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/ItemResource.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/ItemResource.java new file mode 100644 index 0000000000000..8f3a35b3c42ac --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/java/org/acme/qute/ItemResource.java @@ -0,0 +1,41 @@ +package org.acme.qute; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateExtension; +import io.quarkus.qute.TemplateInstance; + +@Path("items") +public class ItemResource { + + @Inject + Template items; + + @GET + @Produces(MediaType.TEXT_HTML) + public TemplateInstance get() { + List data = new ArrayList<>(); + data.add(new Item(new BigDecimal(10), "Apple")); + data.add(new Item(new BigDecimal(16), "Pear")); + data.add(new Item(new BigDecimal(30), "Orange")); + return items.data("items", data); + } + + /** + * This template extension method implements the "discountedPrice" computed property. + */ + @TemplateExtension + static BigDecimal discountedPrice(Item item) { + return item.price.multiply(new BigDecimal("0.9")); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/resources/templates/qute/hello.html b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/resources/templates/qute/hello.html new file mode 100644 index 0000000000000..2d0071f05e1ff --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/resources/templates/qute/hello.html @@ -0,0 +1,10 @@ + + + + +Qute Hello World + + +

Hello {name ?: "world"}!

+ + \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/resources/templates/qute/items.html b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/resources/templates/qute/items.html new file mode 100644 index 0000000000000..06a0101765411 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/main/resources/templates/qute/items.html @@ -0,0 +1,24 @@ +{! This parameter declarations makes it possible to validate expressions in the template !} +{@java.util.List items} + + + + +Qute - Items + + +

List of Items

+
    + {#for item in items} +
  • + {item.name}: + {#if item.price < 15} + {item.price} + {#else} + {item.price} {item.discountedPrice} + {/if} +
  • + {/for} +
+ + \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/HelloResourceTest.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/HelloResourceTest.java new file mode 100644 index 0000000000000..cd35c21902e7b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/HelloResourceTest.java @@ -0,0 +1,28 @@ +package org.acme.qute; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.containsString; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class HelloResourceTest { + + @Test + public void testEndpoint() { + given() + .when().get("/hello") + .then() + .statusCode(200) + .body(containsString("

Hello world!

")); + + given() + .when().get("/hello?name=Lucie") + .then() + .statusCode(200) + .body(containsString("

Hello Lucie!

")); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/ItemsResourceTest.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/ItemsResourceTest.java new file mode 100644 index 0000000000000..a64284779d983 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/ItemsResourceTest.java @@ -0,0 +1,22 @@ +package org.acme.qute; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.containsString; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class ItemsResourceTest { + + @Test + public void testEndpoint() { + given() + .when().get("/items") + .then() + .statusCode(200) + .body(containsString("Apple:"), containsString("30 27.0")); + } + +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/NativeHelloResourceIT.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/NativeHelloResourceIT.java new file mode 100644 index 0000000000000..51e21a7106ce3 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/qute-example/java/src/test/java/org/acme/qute/NativeHelloResourceIT.java @@ -0,0 +1,9 @@ +package org.acme.qute; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class NativeHelloResourceIT extends HelloResourceTest { + + // Execute the same tests but in native mode. +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/base/README.w-append.md b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/base/README.w-append.md new file mode 100644 index 0000000000000..ece65436dc376 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/base/README.w-append.md @@ -0,0 +1,5 @@ +# RESTEasy JAX-RS + +Guide: https://quarkus.io/guides/rest-json + + diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/codestart.yml b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/codestart.yml new file mode 100644 index 0000000000000..d5d9dc4fd762b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/codestart.yml @@ -0,0 +1,12 @@ +--- +name: resteasy-example +ref: resteasy +type: example +spec: + base: + data: + rest: + path: "/hello" + response: "hello" + test-dependencies: + - io.rest-assured:rest-assured diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/main/java/org/acme/resteasy/ExampleResource.r-qute.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/main/java/org/acme/resteasy/ExampleResource.r-qute.java new file mode 100644 index 0000000000000..a605f5be22976 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/main/java/org/acme/resteasy/ExampleResource.r-qute.java @@ -0,0 +1,16 @@ +package org.acme.resteasy; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("{rest.path}") +public class ExampleResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "{rest.response}"; + } +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/main/resources/META-INF/resources/index.html b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000000000..41964305c9c37 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,155 @@ + + + + + code-with-quarkus - 1.0.0-SNAPSHOT + + + + + + +
+
+

Congratulations, you have created a new Quarkus application.

+ +

Why do you see this?

+ +

This page is served by Quarkus. The source is in + src/main/resources/META-INF/resources/index.html.

+ +

What can I do from here?

+ +

If not already done, run the application in dev mode using: mvn compile quarkus:dev. +

+
    +
  • Add REST resources, Servlets, functions and other services in src/main/java.
  • +
  • Your static assets are located in src/main/resources/META-INF/resources.
  • +
  • Configure your application in src/main/resources/application.properties. +
  • +
+ +

Do you like Quarkus?

+

Go give it a star on GitHub.

+ +

How do I get rid of this page?

+

Just delete the src/main/resources/META-INF/resources/index.html file.

+
+
+
+

Application

+
    +
  • GroupId: org.acme
  • +
  • ArtifactId: code-with-quarkus
  • +
  • Version: 1.0.0-SNAPSHOT
  • +
  • Quarkus Version: 1.5.1.Final
  • +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/test/java/org/acme/resteasy/ExampleResourceTest.r-qute.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/test/java/org/acme/resteasy/ExampleResourceTest.r-qute.java new file mode 100644 index 0000000000000..2062b430ed275 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/test/java/org/acme/resteasy/ExampleResourceTest.r-qute.java @@ -0,0 +1,21 @@ +package org.acme.resteasy; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +public class ExampleResourceTest { + + @Test + public void testHelloEndpoint() { + given() + .when().get("{rest.path}") + .then() + .statusCode(200) + .body(is("{rest.response}")); + } + +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/test/java/org/acme/resteasy/NativeExampleResourceIT.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/test/java/org/acme/resteasy/NativeExampleResourceIT.java new file mode 100644 index 0000000000000..0a94d8d9ce4e6 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/java/src/test/java/org/acme/resteasy/NativeExampleResourceIT.java @@ -0,0 +1,9 @@ +package org.acme.resteasy; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class NativeExampleResourceIT extends ExampleResourceTest { + + // Execute the same tests but in native mode. +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/main/kotlin/org/acme/resteasy/ExampleResource.r-qute.kt b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/main/kotlin/org/acme/resteasy/ExampleResource.r-qute.kt new file mode 100644 index 0000000000000..8b60e4e0cf9cb --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/main/kotlin/org/acme/resteasy/ExampleResource.r-qute.kt @@ -0,0 +1,14 @@ +package org.acme.resteasy + +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType + +@Path("{rest.path}") +class ExampleResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + fun hello() = "{rest.response}" +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/main/resources/META-INF/resources/index.html b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000000000..18d3cd59faca9 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,155 @@ + + + + + kotlin - 1.0.0-SNAPSHOT + + + + + + +
+
+

Congratulations, you have created a new Quarkus application.

+ +

Why do you see this?

+ +

This page is served by Quarkus. The source is in + src/main/resources/META-INF/resources/index.html.

+ +

What can I do from here?

+ +

If not already done, run the application in dev mode using: mvn compile quarkus:dev. +

+
    +
  • Add REST resources, Servlets, functions and other services in src/main/java.
  • +
  • Your static assets are located in src/main/resources/META-INF/resources.
  • +
  • Configure your application in src/main/resources/application.properties. +
  • +
+ +

Do you like Quarkus?

+

Go give it a star on GitHub.

+ +

How do I get rid of this page?

+

Just delete the src/main/resources/META-INF/resources/index.html file.

+
+
+
+

Application

+
    +
  • GroupId: org.acme
  • +
  • ArtifactId: kotlin
  • +
  • Version: 1.0.0-SNAPSHOT
  • +
  • Quarkus Version: 1.5.1.Final
  • +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/test/kotlin/org/acme/resteasy/ExampleResourceTest.r-qute.kt b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/test/kotlin/org/acme/resteasy/ExampleResourceTest.r-qute.kt new file mode 100644 index 0000000000000..c097be9645c3b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/test/kotlin/org/acme/resteasy/ExampleResourceTest.r-qute.kt @@ -0,0 +1,20 @@ +package org.acme.resteasy + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.RestAssured.given +import org.hamcrest.CoreMatchers.`is` +import org.junit.jupiter.api.Test + +@QuarkusTest +class ExampleResourceTest { + + @Test + fun testHelloEndpoint() { + given() + .`when`().get("{rest.path}") + .then() + .statusCode(200) + .body(`is`("{rest.response}")) + } + +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/test/kotlin/org/acme/resteasy/NativeExampleResourceIT.kt b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/test/kotlin/org/acme/resteasy/NativeExampleResourceIT.kt new file mode 100644 index 0000000000000..edf12f47ef27e --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/kotlin/src/test/kotlin/org/acme/resteasy/NativeExampleResourceIT.kt @@ -0,0 +1,6 @@ +package org.acme.resteasy + +import io.quarkus.test.junit.NativeImageTest + +@NativeImageTest +class NativeExampleResourceIT : ExampleResourceTest() \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/main/resources/META-INF/resources/index.html b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000000000..9e740c3acd192 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,155 @@ + + + + + scala - 1.0.0-SNAPSHOT + + + + + + +
+
+

Congratulations, you have created a new Quarkus application.

+ +

Why do you see this?

+ +

This page is served by Quarkus. The source is in + src/main/resources/META-INF/resources/index.html.

+ +

What can I do from here?

+ +

If not already done, run the application in dev mode using: mvn compile quarkus:dev. +

+
    +
  • Add REST resources, Servlets, functions and other services in src/main/java.
  • +
  • Your static assets are located in src/main/resources/META-INF/resources.
  • +
  • Configure your application in src/main/resources/application.properties. +
  • +
+ +

Do you like Quarkus?

+

Go give it a star on GitHub.

+ +

How do I get rid of this page?

+

Just delete the src/main/resources/META-INF/resources/index.html file.

+
+
+
+

Application

+
    +
  • GroupId: org.acme
  • +
  • ArtifactId: scala
  • +
  • Version: 1.0.0-SNAPSHOT
  • +
  • Quarkus Version: 1.5.1.Final
  • +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/main/scala/org/acme/resteasy/ExampleResource.r-qute.scala b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/main/scala/org/acme/resteasy/ExampleResource.r-qute.scala new file mode 100644 index 0000000000000..d1164266ad7d7 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/main/scala/org/acme/resteasy/ExampleResource.r-qute.scala @@ -0,0 +1,12 @@ +package org.acme.resteasy + +import javax.ws.rs.\{GET, Path, Produces} +import javax.ws.rs.core.MediaType + +@Path("{rest.path}") +class ExampleResource { + + @GET + @Produces(Array[String](MediaType.TEXT_PLAIN)) + def hello() = "{rest.response}" +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/test/scala/org/acme/resteasy/ExampleResourceTest.r-qute.scala b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/test/scala/org/acme/resteasy/ExampleResourceTest.r-qute.scala new file mode 100644 index 0000000000000..fb8524d9d849b --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/test/scala/org/acme/resteasy/ExampleResourceTest.r-qute.scala @@ -0,0 +1,20 @@ +package org.acme.resteasy + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.RestAssured.given +import org.hamcrest.CoreMatchers.`is` +import org.junit.jupiter.api.Test + +@QuarkusTest +class ExampleResourceTest { + + @Test + def testHelloEndpoint() = { + given() + .`when`().get("{rest.path}") + .then() + .statusCode(200) + .body(`is`("{rest.response}")) + } + +} \ No newline at end of file diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/test/scala/org/acme/resteasy/NativeExampleResourceIT.scala b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/test/scala/org/acme/resteasy/NativeExampleResourceIT.scala new file mode 100644 index 0000000000000..5227f9fe33c38 --- /dev/null +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/resteasy-example/scala/src/test/scala/org/acme/resteasy/NativeExampleResourceIT.scala @@ -0,0 +1,6 @@ +package org.acme.resteasy + +import io.quarkus.test.junit.NativeImageTest + +@NativeImageTest +class NativeExampleResourceIT extends ExampleResourceTest \ No newline at end of file diff --git a/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml index be50cd6e20284..d6fc7cb746bbb 100644 --- a/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/config-yaml/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,3 +9,4 @@ metadata: - "core" status: "stable" guide: "https://quarkus.io/guides/config#yaml" + codestart: "config-yaml" diff --git a/extensions/kotlin/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/kotlin/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 0d3399729f86f..12321b3a0d317 100644 --- a/extensions/kotlin/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/kotlin/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -7,3 +7,4 @@ metadata: categories: - "alt-languages" status: "preview" + codestart: "kotlin" diff --git a/extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml index dc31829fad11f..7987ae7f2a816 100644 --- a/extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/qute/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -8,3 +8,4 @@ metadata: categories: - "miscellaneous" status: "experimental" + codestart: "qute" diff --git a/extensions/resteasy/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/resteasy/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 63e9dffb8c5a2..b2bd1ccb0bc06 100644 --- a/extensions/resteasy/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/resteasy/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -11,3 +11,4 @@ metadata: categories: - "web" status: "stable" + codestart: "resteasy" diff --git a/extensions/scala/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/scala/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 24e2190eb812d..bf87dffc9b575 100644 --- a/extensions/scala/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/scala/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -6,3 +6,4 @@ metadata: categories: - "alt-languages" status: "preview" + codestart: "scala" diff --git a/independent-projects/tools/codestarts/codestarts.adoc b/independent-projects/tools/codestarts/codestarts.adoc new file mode 100644 index 0000000000000..c1c2536bde733 --- /dev/null +++ b/independent-projects/tools/codestarts/codestarts.adoc @@ -0,0 +1,196 @@ += Quarkus - Codestarts + +This guide explains how to create and configure a Quarkus Codestart for an extension. + +== Description + +"Codestarts" is the name we gave to our Quarkus quickstart code generation system. +Codestarts provide a personalized Quarkus getting started experience and really show the Quarkus breadth. +Quarkus extensions are able to provide one or more well defined codestarts which will contain the necessary resources and code examples required to get started using it. + +== How it works + +There are two kinds of codestarts contributing to the generation of a project, the kind where we want to have only one for a project (called Base) and the rest (called Extra). + +Base: + +* project: The project skeleton (e.g. a Quarkus project) +* buildtool: The build tool (e.g. Maven, Gradle, Gradle with Kotlin DSL) +* language: The coding language (e.g. Java, Kotlin, Scala) +* config: The config type (e.g. yaml, properties) + +Extra: +* tooling: Anything that can be added to improve the project (e.g. dockerfiles) +* example: Any Quarkus extension can provide example code. The user can decide to activate it or not. + +Each codestart consists of: + +. A specific directory +. A `codestart.yml` file +. Optionally some templates which follow a structure and naming conventions + +*NOTE* The `codestart.yml` file and the directory structure follow the same principle, it can optionally contain a base and/or some language overrides. + +=== Project generation + +When generating a Quarkus project: + +. Codestarts to use are resolved depending on the input +. The Codestarts' shared data is processed to make it available for all codestarts +. The relevant files are processed: +** Only the files that are related to the selected language are processed +** The files are processed differently based on a naming convention. +** The data is used to render Qute templates +** We always process in this order: language, project, buildtool, config, custom, other. + +The data used to generate a specific codestart is a merge of: + +. The data of the codestart to generate +. All codestarts "shared" data +. The user input +. Some specific post processing (e.g. adding dependencies) + +*NOTE* The data (shared or not) can also be specific to a language. + +=== Directory Structure + +*NOTE* `codestart.yml` is the only required file. + +* `codestart.yml` must be at the root of the codestart +* `./base` contains all the files that will be processed +* `./[languageName]` contains all the files that will be processed if the specified language has been selected (overriding base) + +=== codestart.yml + +codestart.yml: +[source,yaml] +---- +name: the unique name - REQUIRED +ref: the reference name (not unique) to use for extension matching (else the name is used) +type: the type [example (default), project, buildtool, language, config] or any custom type +fallback: flag to indicate that it should be selected as fallback when no codestart has been selected with type - Only for base types +preselected: flag to indicate that it should be pre-selected - Only for extra types. +spec: + [base or language name]: the specification (base, java, ...) + data: a map of data to use only for this codestart + shared-data: a map of data to use accross all codestarts + dependencies: a list of dependencies to add when this is selected + test-dependencies: a list of test dependencies to add when this is selected +---- + +=== Files Naming Convention + +* containing `.r-qute` will be processed with Qute +* containing `.r-qute-include` is used as a Qute template for inclusion. + + Example: When using `{#include [name]}` in a template, it will look for `[name].r-qute-include` in the language dir, then in the base dir. + +* containing `.w-append` will be appended together +* containing `.w-config.yml` will be merged together and automatically converted to the selected config type (yml or properties) +* other files are just copied + +*NOTE* There is also something specific for `pom.xml` but this should not be available out of internal codestarts. + +=== Writing Example Code for an Extension + +Let's imagine we are writing the example codestart for my "foo" extension. As we discussed before, the base of the project is already provided so we can focus on the example code. + +1. Create your `codestart.yml`: + +codestart.yml: +[source,yaml] +---- +--- +name: foo-example +ref: foo +type: example +spec: + base: + data: + # Some data for my templates which gives could be overridden by the user input + some: + data: + here: bar + there: foo + dependencies: + # the dependencies needed for my codestart (note that the extension dependency is auto-added if missing) + - io.quarkus:quarkus-foo + - io.quarkus:quarkus-resteasy + # ... you can also provide a version (when it's not part of the platform bom) + - group:artifact:version + test-dependencies: + # test dependencies + - io.rest-assured:rest-assured +---- + +*NOTE* You don't need to add `pom.xml` or `build.gradle`, it is going to be auto generated. It will include the dependencies provided in the `codestart.yml`. + +2. Add you example code in java, kotlin or scala + +* java/src/main/java/org/acme/foo/Foo.java: +[source,java] +---- +package org.acme.foo + +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType + +@Path("foo") +class ExampleResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + fun hello() = "Hello foo" +} +---- + +* kotlin/src/main/kotlin/org/acme/foo/Foo.kt +* scala/src/main/scala/org/acme/foo/Foo.kt + +*NOTE* Most of the time you won't need it for code, but you can use a `base` directory to add files to process for all languages. +*NOTE* If a language is not implemented, it will ignore this codestart for that language. + +2. Add some configuration (if needed) + +The config `.w-config.yml` file is going to be merged with the other codestarts config and automatically converted to the selected config type (yaml or properties). + +Note: if it's specific to a language, you can put the config in each languages folders + +* base/src/main/resources/application.w-config.yml (using quarkus yaml config convention): +[source,yaml] +---- +quarkus: + http: + port: 8081 +---- + +3. Create a readme section (if needed) + +This will be appended to all different selected examples. + +* base/readme.append.md +[source,markdown] +---- +# FOO Example + +The Foo Example shows... +---- + +=== Tips for writing extension example code + +- You don't need to care about buildtool, dockerfiles, ... +- Use the package `org.acme.[unique-name]` for your sources. +- Examples in different codestarts must be independent, only the config and the build file are merged. +- Write the config in `src/main/resources/application.config.yml`. +It is going to be merged with the other codestarts config and automatically converted to the selected config type (yaml or properties). +- you can add languages independently + + +== Problems to solve + +- How to test that (in combinations)? +- Dependencies conflicts? +- Config conflicts? +- Versioning? diff --git a/independent-projects/tools/codestarts/pom.xml b/independent-projects/tools/codestarts/pom.xml new file mode 100644 index 0000000000000..d786395b1e6b8 --- /dev/null +++ b/independent-projects/tools/codestarts/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + + io.quarkus + quarkus-tools-parent + 999-SNAPSHOT + + + quarkus-devtools-codestarts + Quarkus - Dev tools - Codestarts + + + + io.quarkus.qute + qute-generator + + + io.quarkus + quarkus-bootstrap-app-model + + + io.quarkus + quarkus-platform-descriptor-api + + + io.fabric8 + maven-model-helper + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + commons-io + commons-io + + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + test + + + diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Codestart.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Codestart.java new file mode 100644 index 0000000000000..b72623d795192 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Codestart.java @@ -0,0 +1,40 @@ +package io.quarkus.devtools.codestarts; + +import java.util.Map; +import java.util.stream.Stream; + +final class Codestart { + public static final String BASE_LANGUAGE = "base"; + private final String resourceName; + private final CodestartSpec spec; + + public Codestart(final String resourceName, final CodestartSpec spec) { + this.resourceName = resourceName; + this.spec = spec; + } + + public String getResourceName() { + return resourceName; + } + + public CodestartSpec getSpec() { + return spec; + } + + public Map getLocalData(String languageName) { + return Maps.deepMerge(Stream.of(getBaseLanguageSpec().getLocalData(), getLanguageSpec(languageName).getLocalData())); + } + + public Map getSharedData(String languageName) { + return Maps.deepMerge(Stream.of(getBaseLanguageSpec().getSharedData(), getLanguageSpec(languageName).getSharedData())); + } + + public CodestartSpec.LanguageSpec getBaseLanguageSpec() { + return getSpec().getLanguagesSpec().getOrDefault(BASE_LANGUAGE, new CodestartSpec.LanguageSpec()); + } + + public CodestartSpec.LanguageSpec getLanguageSpec(String languageName) { + return getSpec().getLanguagesSpec().getOrDefault(languageName, new CodestartSpec.LanguageSpec()); + } + +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartData.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartData.java new file mode 100644 index 0000000000000..7d8fb0c0e0c92 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartData.java @@ -0,0 +1,88 @@ +package io.quarkus.devtools.codestarts; + +import io.quarkus.bootstrap.model.AppArtifactKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class CodestartData { + + private CodestartData() { + } + + public enum LegacySupport { + BOM_GROUP_ID("quarkus.platform.group-id", "bom_groupId"), + BOM_ARTIFACT_ID("quarkus.platform.artifact-id", "bom_artifactId"), + BOM_VERSION("quarkus.platform.version", "bom_version"), + PROJECT_GROUP_ID("project.group-id", "project_groupId"), + PROJECT_ARTIFACT_ID("project.artifact-id", "project_artifactId"), + PROJECT_VERSION("project.version", "project_version"), + QUARKUS_PLUGIN_GROUP_ID("quarkus.plugin.group-id", "plugin_groupId"), + QUARKUS_PLUGIN_ARTIFACT_ID("quarkus.plugin.artifact-id", "plugin_artifactId"), + QUARKUS_PLUGIN_VERSION("quarkus.plugin.version", "plugin_version"), + QUARKUS_VERSION("quarkus.version", "quarkus_version"), + BUILDTOOL("buildtool.name", null), + // MAVEN_REPOSITORIES("maven_repositories"), // TODO add compatibility with repo override + // MAVEN_PLUGIN_REPOSITORIES("maven_plugin_repositories"), // TODO add compatibility + ; + + private final String key; + private final String legacyKey; + + LegacySupport(String key, String legacyKey) { + this.key = key; + this.legacyKey = legacyKey; + } + + public String getKey() { + return key; + } + + public String getLegacyKey() { + return legacyKey; + } + + public static Map convertFromLegacy(Map legacy) { + return Maps.unflatten(Stream.of(values()) + .filter(v -> v.getLegacyKey() != null) + .filter(v -> legacy.containsKey(v.getLegacyKey())) + .map(v -> new HashMap.SimpleImmutableEntry<>(v.getKey(), legacy.get(v.getLegacyKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + } + } + + static Map buildCodestartData(final Codestart codestart, final String languageName, + final Map data) { + return Maps.deepMerge(Stream.of(codestart.getLocalData(languageName), data)); + } + + public static Map buildCodestartProjectData(Collection codestarts) { + final HashMap data = new HashMap<>(); + codestarts.forEach((c) -> data.put("codestart-project." + c.getSpec().getType().toString().toLowerCase() + ".name", + c.getSpec().getName())); + return Maps.unflatten(data); + } + + static Map buildDependenciesData(Stream codestartsStream, String languageName, + Collection extensions) { + final Map> depsData = new HashMap<>(); + final List extensionsAsDeps = extensions.stream() + .map(k -> k.getGroupId() + ":" + k.getArtifactId()).map(CodestartSpec.CodestartDep::new) + .collect(Collectors.toList()); + depsData.put("dependencies", new ArrayList<>(extensionsAsDeps)); + depsData.put("test-dependencies", new ArrayList<>()); + codestartsStream + .flatMap(s -> Stream.of(s.getBaseLanguageSpec(), s.getLanguageSpec(languageName))) + .forEach(d -> { + depsData.get("dependencies").addAll(d.getDependencies()); + depsData.get("test-dependencies").addAll(d.getTestDependencies()); + }); + return Collections.unmodifiableMap(depsData); + } + +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartException.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartException.java new file mode 100644 index 0000000000000..8264f53aee205 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartException.java @@ -0,0 +1,13 @@ +package io.quarkus.devtools.codestarts; + +public class CodestartException extends RuntimeException { + + public CodestartException(Throwable cause) { + super(null, cause); + } + + public CodestartException(String message) { + super(message, null); + } + +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartInput.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartInput.java new file mode 100644 index 0000000000000..2204d691594c5 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartInput.java @@ -0,0 +1,47 @@ +package io.quarkus.devtools.codestarts; + +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import java.util.Collection; +import java.util.Map; + +public class CodestartInput { + private final QuarkusPlatformDescriptor descriptor; + private final Collection extensions; + private final boolean includeExamples; + private final Map data; + private final Collection codestarts; + + CodestartInput(QuarkusPlatformDescriptor descriptor, Collection extensions, + Collection codestarts, boolean includeExamples, Map data) { + this.descriptor = descriptor; + this.extensions = extensions; + this.codestarts = codestarts; + this.includeExamples = includeExamples; + this.data = data; + } + + public static CodestartInputBuilder builder(QuarkusPlatformDescriptor descriptor) { + return new CodestartInputBuilder(descriptor); + } + + public QuarkusPlatformDescriptor getDescriptor() { + return descriptor; + } + + public Collection getCodestarts() { + return codestarts; + } + + public Collection getExtensions() { + return extensions; + } + + public boolean includeExamples() { + return includeExamples; + } + + public Map getData() { + return data; + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartInputBuilder.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartInputBuilder.java new file mode 100644 index 0000000000000..de3e7fdd058a6 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartInputBuilder.java @@ -0,0 +1,77 @@ +package io.quarkus.devtools.codestarts; + +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.dependencies.Extension; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class CodestartInputBuilder { + private QuarkusPlatformDescriptor descriptor; + private Collection extensions = new ArrayList<>(); + private Collection codestarts = new ArrayList<>(); + private boolean includeExamples = false; + private Map data = new HashMap<>(); + + CodestartInputBuilder(QuarkusPlatformDescriptor descriptor) { + this.descriptor = descriptor; + } + + public CodestartInputBuilder addExtensions(Collection extensions) { + this.extensions.addAll(extensions); + this.addCodestarts(extractCodestartNamesFromExtensions(descriptor, extensions)); + return this; + } + + public CodestartInputBuilder addExtension(AppArtifactKey extension) { + return this.addExtensions(Collections.singletonList(extension)); + } + + public CodestartInputBuilder addCodestarts(Collection codestarts) { + this.codestarts.addAll(codestarts); + return this; + } + + public CodestartInputBuilder includeExamples() { + return includeExamples(true); + } + + public CodestartInputBuilder includeExamples(boolean includeExamples) { + this.includeExamples = includeExamples; + return this; + } + + public CodestartInputBuilder addData(Map data) { + this.data.putAll(data); + return this; + } + + public CodestartInputBuilder addCodestart(String name) { + this.codestarts.add(name); + return this; + } + + public CodestartInputBuilder putData(String key, Object value) { + this.data.put(key, value); + return this; + } + + public CodestartInput build() { + return new CodestartInput(descriptor, extensions, codestarts, includeExamples, Maps.unflatten(data)); + } + + private static Set extractCodestartNamesFromExtensions(QuarkusPlatformDescriptor descriptor, + Collection extensions) { + return descriptor.getExtensions().stream() + .filter(e -> extensions + .contains(new AppArtifactKey(e.getGroupId(), e.getArtifactId(), e.getClassifier(), + e.getType() == null ? "jar" : e.getType()))) + .map(Extension::getCodestart) + .collect(Collectors.toSet()); + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartLoader.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartLoader.java new file mode 100644 index 0000000000000..26a18e9e1f9d1 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartLoader.java @@ -0,0 +1,66 @@ +package io.quarkus.devtools.codestarts; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.io.FilenameUtils; + +final class CodestartLoader { + private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()) + .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING) + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); + + private static final String CODESTARTS_DIR_BUNDLED = "bundled-codestarts"; + private static final String CODESTARTS_DIR_FROM_EXTENSIONS = "codestarts"; + + private CodestartLoader() { + } + + public static List loadAllCodestarts(CodestartInput input) throws IOException { + return Stream.concat(loadBundledCodestarts(input).stream(), + loadCodestartsFromExtensions(input).stream()).collect(Collectors.toList()); + } + + public static Collection loadBundledCodestarts(CodestartInput input) throws IOException { + return loadCodestarts(input.getDescriptor(), CODESTARTS_DIR_BUNDLED); + } + + public static Collection loadCodestartsFromExtensions(CodestartInput input) + throws IOException { + // TODO resolve codestarts which live inside extensions. Using a directory is just a temporary workaround. + return loadCodestarts(input.getDescriptor(), CODESTARTS_DIR_FROM_EXTENSIONS); + } + + static Collection loadCodestarts(final QuarkusPlatformDescriptor descriptor, final String directoryName) + throws IOException { + return descriptor.loadResourcePath(directoryName, + path -> toResourceNameWalker(directoryName, path).filter(n -> n.matches(".*/codestart\\.ya?ml$")) + .map(n -> { + try { + final CodestartSpec spec = YAML_MAPPER.readerFor(CodestartSpec.class) + .readValue(descriptor.getTemplate(n)); + return new Codestart(n.replaceAll("/?codestart\\.ya?ml", ""), spec); + } catch (IOException e) { + throw new UncheckedIOException("Failed to parse codestart spec: " + n, e); + } + }).collect(Collectors.toList())); + } + + private static Stream toResourceNameWalker(final String dirName, final Path dirPath) throws IOException { + return Files.walk(dirPath).map(p -> resolveResourceName(dirName, dirPath, p)); + } + + private static String resolveResourceName(final String dirName, final Path dirPath, final Path resourcePath) { + return FilenameUtils.concat(dirName, dirPath.relativize(resourcePath).toString().replace('\\', '/')); + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartProcessor.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartProcessor.java new file mode 100644 index 0000000000000..da0b611f43d12 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartProcessor.java @@ -0,0 +1,147 @@ +package io.quarkus.devtools.codestarts; + +import static io.quarkus.devtools.codestarts.Codestart.BASE_LANGUAGE; + +import io.quarkus.devtools.codestarts.reader.CodestartFileReader; +import io.quarkus.devtools.codestarts.reader.QuteCodestartFileReader; +import io.quarkus.devtools.codestarts.writer.AppenderCodestartFileWriter; +import io.quarkus.devtools.codestarts.writer.CodestartFileWriter; +import io.quarkus.devtools.codestarts.writer.MavenPomMergeCodestartFileWriter; +import io.quarkus.devtools.codestarts.writer.SmartConfigMergeCodestartFileWriter; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +final class CodestartProcessor implements Closeable { + + private final QuarkusPlatformDescriptor descriptor; + private final String languageName; + private final Path targetDirectory; + private final Map data; + private final List readers; + private final List writers; + + CodestartProcessor(final QuarkusPlatformDescriptor descriptor, final String languageName, final Path targetDirectory, + final Map data) { + this.descriptor = descriptor; + this.languageName = languageName; + this.targetDirectory = targetDirectory; + this.data = data; + this.readers = newCodestartFileReaders(); + this.writers = newCodestartFileWriters(); + } + + void process(final Codestart codestart) throws IOException { + descriptor.loadResourcePath(codestart.getResourceName(), p -> { + resolveDirectoriesToProcessAsStream(p, languageName) + .forEach(dirPath -> processCodestartDir(dirPath, + CodestartData.buildCodestartData(codestart, languageName, data))); + return null; + }); + } + + static Stream resolveDirectoriesToProcessAsStream(final Path sourceDirectory, final String languageName) + throws IOException { + if (!Files.isDirectory(sourceDirectory)) { + throw new IllegalStateException("Codestart sourceDirectory is not a directory: " + sourceDirectory); + } + return Stream.of(BASE_LANGUAGE, languageName) + .map(sourceDirectory::resolve) + .filter(Files::isDirectory); + } + + void processCodestartDir(final Path sourceDirectory, final Map finalData) { + + try { + final Collection sources = Files.walk(sourceDirectory) + .filter(path -> !path.equals(sourceDirectory)) + .collect(Collectors.toList()); + for (Path sourcePath : sources) { + final Path relativePath = sourceDirectory.relativize(sourcePath); + if (!Files.isDirectory(sourcePath)) { + final String fileName = relativePath.getFileName().toString(); + final Path targetPath = targetDirectory.resolve(relativePath.toString()); + final Optional possibleReader = readers.stream() + .filter(r -> r.matches(fileName)) + .findFirst(); + final Optional possibleWriter = writers.stream() + .filter(r -> r.matches(fileName)) + .findFirst(); + + if (!possibleWriter.isPresent() && !possibleReader.isPresent()) { + processStaticFile(sourcePath, targetPath); + } else { + final CodestartFileReader reader = possibleReader.orElse(CodestartFileReader.DEFAULT); + final Optional content = reader.read(sourcePath, + languageName, finalData); + if (content.isPresent()) { + final CodestartFileWriter writer = possibleWriter.orElse(CodestartFileWriter.DEFAULT); + final String cleanFileName = writer.cleanFileName(reader.cleanFileName(fileName)); + final Path parent = targetPath.getParent(); + Files.createDirectories(parent); + writer.process(content.get(), parent.resolve(cleanFileName), + languageName, finalData); + } + } + } + } + ; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } + + private static void processStaticFile(Path path, Path targetPath) throws IOException { + Files.createDirectories(targetPath.getParent()); + Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING); + } + + void checkTargetDir() throws IOException { + if (!Files.exists(targetDirectory)) { + boolean mkdirStatus = targetDirectory.toFile().mkdirs(); + if (!mkdirStatus) { + throw new IOException("Failed to create the project directory: " + targetDirectory); + } + return; + } + if (!Files.isDirectory(targetDirectory)) { + throw new IOException("Project path needs to point to a directory: " + targetDirectory); + } + final String[] files = targetDirectory.toFile().list(); + if (files != null && files.length > 0) { + throw new IOException("You can't create a project when the directory is not empty: " + targetDirectory); + } + } + + static List newCodestartFileReaders() { + return Collections.unmodifiableList(Arrays.asList( + new QuteCodestartFileReader())); + } + + static List newCodestartFileWriters() { + return Collections.unmodifiableList(Arrays.asList( + new AppenderCodestartFileWriter(), + new MavenPomMergeCodestartFileWriter(), + new SmartConfigMergeCodestartFileWriter())); + } + + @Override + public void close() throws IOException { + for (CodestartFileWriter writer : writers) { + writer.close(); + } + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartProject.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartProject.java new file mode 100644 index 0000000000000..8492cde1e051c --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartProject.java @@ -0,0 +1,72 @@ +package io.quarkus.devtools.codestarts; + +import static io.quarkus.devtools.codestarts.CodestartData.buildCodestartProjectData; +import static io.quarkus.devtools.codestarts.CodestartData.buildDependenciesData; + +import io.quarkus.devtools.codestarts.CodestartSpec.Type; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class CodestartProject { + + private final List codestarts; + private final CodestartInput codestartInput; + + CodestartProject(CodestartInput codestartInput, List codestarts) { + this.codestartInput = Objects.requireNonNull(codestartInput, "codestartInput is required"); + this.codestarts = Objects.requireNonNull(codestarts, "codestarts is required"); + checkContainsType(Type.PROJECT); + checkContainsType(Type.LANGUAGE); + } + + public List getCodestarts() { + return codestarts; + } + + public CodestartInput getCodestartInput() { + return codestartInput; + } + + public Optional getCodestart(Type type) { + return codestarts.stream().filter(c -> c.getSpec().getType() == type).findFirst(); + } + + public Codestart getRequiredCodestart(Type type) { + return checkContainsType(type); + } + + public String getLanguageName() { + return getRequiredCodestart(Type.LANGUAGE).getSpec().getName(); + } + + public Map getSharedData() { + final Stream> codestartsGlobal = getCodestarts().stream() + .map(c -> c.getSharedData(getLanguageName())); + return Maps.deepMerge(Stream.concat(codestartsGlobal, Stream.of(getCodestartInput().getData()))); + } + + public Map getDepsData() { + return buildDependenciesData(getCodestarts().stream(), getLanguageName(), getCodestartInput().getExtensions()); + } + + public Map getCodestartProjectData() { + return buildCodestartProjectData(getBaseCodestarts()); + } + + List getBaseCodestarts() { + return getCodestarts().stream().filter(c -> c.getSpec().getType().isBase()).collect(Collectors.toList()); + } + + List getExtraCodestarts() { + return getCodestarts().stream().filter(c -> !c.getSpec().getType().isBase()).collect(Collectors.toList()); + } + + private Codestart checkContainsType(Type type) { + return getCodestart(type) + .orElseThrow(() -> new IllegalArgumentException(type.toString().toLowerCase() + " Codestart is required")); + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartSpec.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartSpec.java new file mode 100644 index 0000000000000..3931da78b39df --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/CodestartSpec.java @@ -0,0 +1,158 @@ +package io.quarkus.devtools.codestarts; + +import static java.util.Objects.requireNonNull; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +final class CodestartSpec { + + enum Type { + PROJECT(true), + LANGUAGE(true), + BUILDTOOL(true), + CONFIG(true), + EXAMPLE(false), + TOOLING(false); + + private final boolean base; + + Type(boolean base) { + this.base = base; + } + + public boolean isBase() { + return base; + } + } + + private final String name; + private final boolean isPreselected; + private final String ref; + private final Type type; + private final boolean isFallback; + private final Map languagesSpec; + + @JsonCreator + public CodestartSpec(@JsonProperty(value = "name", required = true) String name, + @JsonProperty(value = "ref") String ref, + @JsonProperty(value = "type") Type type, + @JsonProperty("fallback") boolean isFallback, + @JsonProperty("preselected") boolean isPreselected, + @JsonProperty("spec") Map languagesSpec) { + this.name = requireNonNull(name, "name is required"); + this.ref = ref != null ? ref : name; + this.type = type != null ? type : Type.EXAMPLE; + this.isFallback = isFallback; + this.isPreselected = isPreselected; + this.languagesSpec = languagesSpec != null ? languagesSpec : Collections.emptyMap(); + } + + public String getName() { + return name; + } + + public String getRef() { + return ref; + } + + public Type getType() { + return type; + } + + public boolean isFallback() { + return isFallback; + } + + public boolean isPreselected() { + return isPreselected; + } + + public boolean isExample() { + return getType() == Type.EXAMPLE; + } + + public Map getLanguagesSpec() { + return languagesSpec; + } + + public static final class LanguageSpec { + private final Map localData; + private final Map sharedData; + private final List dependencies; + private final List testDependencies; + + public LanguageSpec() { + this(null, null, null, null); + } + + @JsonCreator + public LanguageSpec(@JsonProperty("data") Map localData, + @JsonProperty("shared-data") Map sharedData, + @JsonProperty("dependencies") List dependencies, + @JsonProperty("test-dependencies") List testDependencies) { + this.localData = localData != null ? localData : Collections.emptyMap(); + this.sharedData = sharedData != null ? sharedData : Collections.emptyMap(); + this.dependencies = dependencies != null ? dependencies : Collections.emptyList(); + this.testDependencies = testDependencies != null ? testDependencies : Collections.emptyList(); + } + + public Map getLocalData() { + return localData; + } + + public Map getSharedData() { + return sharedData; + } + + public List getDependencies() { + return dependencies; + } + + public List getTestDependencies() { + return testDependencies; + } + } + + public static class CodestartDep extends HashMap { + public CodestartDep() { + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public CodestartDep(final String expression) { + final String[] split = expression.split(":"); + if (split.length < 2 || split.length > 3) { + throw new IllegalArgumentException("Invalid CodestartDep expression: " + expression); + } + this.put("groupId", split[0]); + this.put("artifactId", split[1]); + if (split.length == 3) { + this.put("version", split[2]); + } + } + + public String getGroupId() { + return this.get("groupId"); + } + + public String getArtifactId() { + return this.get("artifactId"); + } + + public String getVersion() { + return this.get("version"); + } + + @Override + public String toString() { + final String version = Optional.ofNullable(getVersion()).map(v -> ":" + v).orElse(""); + return getGroupId() + ":" + getArtifactId() + version; + } + + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Codestarts.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Codestarts.java new file mode 100644 index 0000000000000..1da7a2d9cb197 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Codestarts.java @@ -0,0 +1,93 @@ +package io.quarkus.devtools.codestarts; + +import static io.quarkus.devtools.codestarts.CodestartData.LegacySupport.BUILDTOOL; +import static io.quarkus.devtools.codestarts.CodestartLoader.loadAllCodestarts; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Codestarts { + + public static CodestartProject prepareProject(final CodestartInput input) throws IOException { + final Optional buildtool = Maps.getNestedDataValue(input.getData(), BUILDTOOL.getKey()); + final Set selectedCodestartNames = Stream.concat( + input.getCodestarts().stream(), + Stream.of(buildtool.orElse(null)).filter(Objects::nonNull)) + .collect(Collectors.toSet()); + + final List allCodestarts = loadAllCodestarts(input); + + final List selectedCodestarts = new ArrayList<>(); + selectedCodestarts.addAll(resolveSelectedBaseCodestarts(allCodestarts, selectedCodestartNames)); + selectedCodestarts.addAll(resolveSelectedExtraCodestarts(input, selectedCodestartNames, allCodestarts)); + + // Hack for CommandMode activation + if (input.includeExamples() && selectedCodestarts.stream().noneMatch(c -> c.getSpec().isExample())) { + final Codestart commandmodeExampleCodestart = allCodestarts.stream() + .filter(c -> c.getSpec().getName().equals("commandmode-example")) + .findFirst().orElseThrow(() -> new IllegalStateException("commandmode-example codestart not found")); + selectedCodestarts.add(commandmodeExampleCodestart); + } + + return new CodestartProject(input, selectedCodestarts); + } + + public static void generateProject(final CodestartProject codestartProject, final Path targetDirectory) throws IOException { + + final String languageName = codestartProject.getLanguageName(); + + // Processing data + final Map data = Maps.deepMerge(Stream.of( + codestartProject.getSharedData(), + codestartProject.getDepsData(), + codestartProject.getCodestartProjectData())); + + CodestartProcessor processor = new CodestartProcessor(codestartProject.getCodestartInput().getDescriptor(), + languageName, targetDirectory, data); + processor.checkTargetDir(); + for (Codestart codestart : codestartProject.getCodestarts()) { + processor.process(codestart); + } + processor.close(); + } + + private static Collection resolveSelectedExtraCodestarts(CodestartInput input, + Set selectedCodestartNames, + Collection allCodestarts) { + return allCodestarts.stream() + .filter(c -> !c.getSpec().getType().isBase()) + .filter(c -> c.getSpec().isPreselected() || selectedCodestartNames.contains(c.getSpec().getRef())) + .filter(c -> !c.getSpec().isExample() || input.includeExamples()) + .collect(Collectors.toList()); + } + + private static Collection resolveSelectedBaseCodestarts(Collection allCodestarts, + Set selectedCodestartNames) { + + return allCodestarts.stream() + .filter(c -> c.getSpec().getType().isBase()) + .filter(c -> c.getSpec().isFallback() || selectedCodestartNames.contains(c.getSpec().getRef())) + .filter(c -> !c.getSpec().isExample()) + .collect(Collectors.toMap(c -> c.getSpec().getType(), c -> c, (a, b) -> { + if (a.getSpec().isFallback() && b.getSpec().isFallback()) { + throw new IllegalStateException( + "Multiple fallback found for a codestart that must be single: " + a.getSpec().getType()); + } + if (!a.getSpec().isFallback() && !b.getSpec().isFallback()) { + throw new IllegalStateException( + "Multiple selection for a codestart that must be single: " + a.getSpec().getType()); + } + return !a.getSpec().isFallback() ? a : b; + })).values(); + } + +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Maps.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Maps.java new file mode 100644 index 0000000000000..1b233a7070e0d --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/Maps.java @@ -0,0 +1,81 @@ +package io.quarkus.devtools.codestarts; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +public final class Maps { + + private Maps() { + } + + @SuppressWarnings("unchecked") + public static Optional getNestedDataValue(Map data, String path) { + if (!path.contains(".")) { + return Optional.ofNullable((T) data.get(path)); + } + int index = path.indexOf("."); + String key = path.substring(0, index); + if (data.get(key) instanceof Map) { + return getNestedDataValue((Map) data.get(key), path.substring(index + 1)); + } else { + return Optional.empty(); + } + + } + + public static Map deepMerge(final Stream> mapStream) { + final Map out = new HashMap<>(); + mapStream.forEach(m -> deepMerge(out, m)); + return out; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static void deepMerge(Map left, Map right) { + for (Object key : right.keySet()) { + if (right.get(key) instanceof Map && left.get(key) instanceof Map) { + Map leftChild = (Map) left.get(key); + Map rightChild = (Map) right.get(key); + deepMerge(leftChild, rightChild); + } else { + // Override + left.put(key, right.get(key)); + } + } + } + + public static Map unflatten(Map flattened) { + Map unflattened = new HashMap<>(); + for (String key : flattened.keySet()) { + doUnflatten(unflattened, key, flattened.get(key)); + } + return unflattened; + } + + private static void doUnflatten(Map current, String key, Object originalValue) { + if (!key.contains(".")) { + return; + } + String[] parts = key.split("\\."); + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if (i == (parts.length - 1)) { + current.put(part, originalValue); + return; + } + + final Object value = current.get(part); + if (value == null) { + final HashMap map = new HashMap<>(); + current.put(part, map); + current = map; + } else if (value instanceof Map) { + current = (Map) value; + } else { + throw new IllegalStateException("Conflicting data types for key '" + key + "'"); + } + } + } + +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/reader/CodestartFileReader.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/reader/CodestartFileReader.java new file mode 100644 index 0000000000000..b97fbc7d5ee6e --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/reader/CodestartFileReader.java @@ -0,0 +1,37 @@ +package io.quarkus.devtools.codestarts.reader; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +public interface CodestartFileReader { + + CodestartFileReader DEFAULT = new DefaultCodestartFileReader(); + + boolean matches(String fileName); + + String cleanFileName(String fileName); + + Optional read(Path sourcePath, String languageName, Map data) throws IOException; + + class DefaultCodestartFileReader implements CodestartFileReader { + + @Override + public boolean matches(String fileName) { + return false; + } + + @Override + public String cleanFileName(String fileName) { + return fileName; + } + + @Override + public Optional read(Path sourcePath, String languageName, Map data) throws IOException { + return Optional.of(new String(Files.readAllBytes(sourcePath), StandardCharsets.UTF_8)); + } + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/reader/QuteCodestartFileReader.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/reader/QuteCodestartFileReader.java new file mode 100644 index 0000000000000..0f4b39ab1b58a --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/reader/QuteCodestartFileReader.java @@ -0,0 +1,109 @@ +package io.quarkus.devtools.codestarts.reader; + +import io.quarkus.qute.Engine; +import io.quarkus.qute.Expression; +import io.quarkus.qute.ResultMapper; +import io.quarkus.qute.Results; +import io.quarkus.qute.TemplateException; +import io.quarkus.qute.TemplateLocator; +import io.quarkus.qute.TemplateNode; +import io.quarkus.qute.Variant; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +public class QuteCodestartFileReader implements CodestartFileReader { + + private static final String FLAG = ".r-qute"; + public static final String INCLUDE_QUTE_FLAG = ".r-qute-include"; + + @Override + public boolean matches(String fileName) { + return fileName.contains(FLAG) || fileName.contains(INCLUDE_QUTE_FLAG); + } + + @Override + public String cleanFileName(String fileName) { + return fileName.replace(FLAG, ""); + } + + public Optional read(Path sourcePath, String languageName, Map data) throws IOException { + if (sourcePath.getFileName().toString().contains(INCLUDE_QUTE_FLAG)) { + return Optional.empty(); + } + return Optional.of(readQuteFile(sourcePath, languageName, data)); + } + + public static Engine newEngine() { + return Engine.builder().addDefaults() + .addResultMapper(new MissingValueMapper()) + .build(); + } + + public static String readQuteFile(Path path, String languageName, Map data) throws IOException { + final String content = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + final Engine engine = Engine.builder().addDefaults() + .addResultMapper(new MissingValueMapper()) + .removeStandaloneLines(true) + .addLocator(id -> findIncludeTemplate(path, languageName, id).map(IncludeTemplateLocation::new)) + .build(); + try { + return engine.parse(content).render(data); + } catch (TemplateException e) { + throw new IOException("Error while rendering template: " + path.toString(), e); + } + } + + private static Optional findIncludeTemplate(Path path, String languageName, String name) { + // FIXME looking at the parent dir is a bit random + final Path codestartPath = path.getParent().getParent(); + final String includeFileName = name + INCLUDE_QUTE_FLAG; + final Path languageIncludeTemplate = codestartPath.resolve(languageName + "/" + includeFileName); + if (Files.isRegularFile(languageIncludeTemplate)) { + return Optional.of(languageIncludeTemplate); + } + final Path baseIncludeTemplate = codestartPath.resolve("base/" + includeFileName); + if (Files.isRegularFile(baseIncludeTemplate)) { + return Optional.of(baseIncludeTemplate); + } + return Optional.empty(); + } + + private static class IncludeTemplateLocation implements TemplateLocator.TemplateLocation { + + private final Path path; + + private IncludeTemplateLocation(Path path) { + this.path = path; + } + + @Override + public Reader read() { + try { + return Files.newBufferedReader(path); + } catch (IOException e) { + return null; + } + } + + @Override + public Optional getVariant() { + return Optional.empty(); + } + } + + static class MissingValueMapper implements ResultMapper { + + public boolean appliesTo(TemplateNode.Origin origin, Object result) { + return Results.Result.NOT_FOUND.equals(result); + } + + public String map(Object result, Expression expression) { + throw new IllegalStateException("Missing required data: {" + expression.toOriginalString() + "}"); + } + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/AppenderCodestartFileWriter.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/AppenderCodestartFileWriter.java new file mode 100644 index 0000000000000..877b61ade4278 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/AppenderCodestartFileWriter.java @@ -0,0 +1,38 @@ +package io.quarkus.devtools.codestarts.writer; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public class AppenderCodestartFileWriter implements CodestartFileWriter { + + private static final String FLAG = ".w-append"; + + private final Map files = new HashMap<>(); + + @Override + public boolean matches(String fileName) { + return fileName.contains(FLAG); + } + + @Override + public String cleanFileName(String fileName) { + return fileName.replace(FLAG, ""); + } + + @Override + public void process(String content, Path targetPath, String languageName, Map data) throws IOException { + files.putIfAbsent(targetPath, new StringBuilder()); + final StringBuilder builder = files.get(targetPath); + builder.append(builder.length() > 0 ? "\n" + content : content); + } + + @Override + public void close() throws IOException { + for (Map.Entry entry : files.entrySet()) { + Files.write(entry.getKey(), entry.getValue().toString().getBytes()); + } + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/CodestartFileWriter.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/CodestartFileWriter.java new file mode 100644 index 0000000000000..32efd69046631 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/CodestartFileWriter.java @@ -0,0 +1,42 @@ +package io.quarkus.devtools.codestarts.writer; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public interface CodestartFileWriter extends Closeable { + + CodestartFileWriter DEFAULT = new DefaultCodestartFileWriter(); + + boolean matches(String fileName); + + String cleanFileName(String fileName); + + void process(String content, Path targetPath, String languageName, Map data) throws IOException; + + @Override + default void close() throws IOException { + + } + + class DefaultCodestartFileWriter implements CodestartFileWriter { + + @Override + public boolean matches(String fileName) { + return false; + } + + @Override + public String cleanFileName(String fileName) { + return fileName; + } + + @Override + public void process(String content, Path targetPath, String languageName, Map data) throws IOException { + Files.write(targetPath, content.getBytes()); + } + } + +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/MavenPomMergeCodestartFileWriter.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/MavenPomMergeCodestartFileWriter.java new file mode 100644 index 0000000000000..70154c18959c6 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/MavenPomMergeCodestartFileWriter.java @@ -0,0 +1,51 @@ +package io.quarkus.devtools.codestarts.writer; + +import io.fabric8.maven.Maven; +import io.fabric8.maven.merge.SmartModelMerger; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.maven.model.Model; + +public class MavenPomMergeCodestartFileWriter implements CodestartFileWriter { + + private static final String FLAG = ".w-pom-merge"; + + private final Map> models = new HashMap<>(); + + @Override + public boolean matches(String fileName) { + return fileName.contains(FLAG); + } + + @Override + public String cleanFileName(String fileName) { + return fileName.replace(FLAG, ""); + } + + @Override + public void process(String content, Path targetPath, String languageName, Map data) { + if (!Files.exists(targetPath)) { + throw new IllegalStateException( + "Using .part is not possible when the target file does not exist already: " + targetPath); + } + models.putIfAbsent(targetPath, new ArrayList<>()); + models.get(targetPath).add(Maven.readModel(new StringReader(content))); + } + + @Override + public void close() { + for (Map.Entry> entry : models.entrySet()) { + final SmartModelMerger merger = new SmartModelMerger(); + final Model targetModel = Maven.readModel(entry.getKey()); + for (Model model : entry.getValue()) { + merger.merge(targetModel, model, true, null); + } + Maven.writeModel(targetModel); + } + } +} diff --git a/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/SmartConfigMergeCodestartFileWriter.java b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/SmartConfigMergeCodestartFileWriter.java new file mode 100644 index 0000000000000..f669e969c2d02 --- /dev/null +++ b/independent-projects/tools/codestarts/src/main/java/io/quarkus/devtools/codestarts/writer/SmartConfigMergeCodestartFileWriter.java @@ -0,0 +1,107 @@ +package io.quarkus.devtools.codestarts.writer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.quarkus.devtools.codestarts.Maps; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class SmartConfigMergeCodestartFileWriter implements CodestartFileWriter { + + private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); + private static final String FLAG = ".w-config"; + + private final Map> configs = new HashMap<>(); + + @Override + public boolean matches(String fileName) { + return fileName.contains(FLAG); + } + + @Override + public String cleanFileName(String fileName) { + return fileName.replace(FLAG, ""); + } + + @Override + @SuppressWarnings({ "rawtypes" }) + public void process(String content, Path targetPath, String languageName, Map data) throws IOException { + final String fileName = targetPath.getFileName().toString(); + if (!fileName.endsWith(".yml")) { + throw new IllegalStateException("Invalid config-merge file, should be .yml: " + fileName); + } + + final String configType = getConfigType(data); + final Path typedTargetPath = getTypedTargetPath(configType, targetPath); + + configs.putIfAbsent(typedTargetPath, new HashMap<>()); + if (!content.trim().isEmpty()) { + final Map o = YAML_MAPPER.readerFor(Map.class).readValue(content); + Maps.deepMerge(configs.get(typedTargetPath), o); + } + } + + @Override + public void close() throws IOException { + for (Map.Entry> config : configs.entrySet()) { + if (Files.exists(config.getKey())) { + throw new IllegalStateException("This config file should not exists: " + config.getKey().toString()); + } + final String fileName = config.getKey().getFileName().toString(); + if (fileName.endsWith(".yml")) { + writeYamlConfig(config); + } else if (fileName.endsWith(".properties")) { + writePropertiesConfig(config); + } else { + throw new IllegalStateException("Invalid file type: " + fileName); + } + + } + } + + private static void writeYamlConfig(Map.Entry> config) throws IOException { + YAML_MAPPER.writerFor(Map.class).writeValue(config.getKey().toFile(), config.getValue()); + } + + private static void writePropertiesConfig(Map.Entry> config) throws IOException { + final StringBuilder builder = new StringBuilder(); + final HashMap flat = new HashMap<>(); + flatten("", flat, config.getValue()); + for (Map.Entry entry : flat.entrySet()) { + builder.append(entry.getKey()).append("=").append(entry.getValue()).append("\n"); + } + Files.write(config.getKey(), builder.toString().getBytes()); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static void flatten(String prefix, Map target, Map map) { + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof Map) { + flatten(entry.getKey() + ".", target, (Map) entry.getValue()); + } else { + // TODO: handle different types of values + target.put(prefix + entry.getKey(), entry.getValue().toString()); + } + } + } + + private static Path getTypedTargetPath(String configType, Path targetPath) { + if (Objects.equals(configType, "config-properties")) { + return targetPath.getParent().resolve(targetPath.getFileName().toString().replace(".yml", ".properties")); + } + if (Objects.equals(configType, "config-yaml")) { + return targetPath; + } + throw new IllegalStateException("Unsupported config type: " + configType); + } + + private static String getConfigType(Map data) { + final Optional config = Maps.getNestedDataValue(data, "codestart-project.config.name"); + return config.orElseThrow(() -> new IllegalStateException("Config type is required")); + } +} diff --git a/independent-projects/tools/devtools-common/pom.xml b/independent-projects/tools/devtools-common/pom.xml index 1c173d9c28919..3655ef79f5826 100644 --- a/independent-projects/tools/devtools-common/pom.xml +++ b/independent-projects/tools/devtools-common/pom.xml @@ -34,6 +34,14 @@ io.quarkus quarkus-devtools-utilities + + io.quarkus + quarkus-devtools-codestarts + + + io.quarkus.qute + qute-generator + io.quarkus quarkus-platform-descriptor-api @@ -42,6 +50,18 @@ org.apache.commons commons-compress + + io.fabric8 + maven-model-helper + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + org.apache.maven maven-plugin-api diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java index cd4e67689f1ed..a36a98c8d8a43 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java @@ -101,6 +101,16 @@ public CreateProject extensions(Set extensions) { return this; } + public CreateProject codestartsEnabled(boolean codestartsEnabled) { + setValue("codestarts.enabled", codestartsEnabled); + return this; + } + + public CreateProject withExampleCode(boolean withExampleCode) { + setValue("codestarts.with-example-code", withExampleCode); + return this; + } + public CreateProject setValue(String name, Object value) { values.put(name, value); return this; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java index 1ac7a7376c709..4cb1d977ea32a 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java @@ -4,14 +4,19 @@ import static io.quarkus.devtools.project.codegen.ProjectGenerator.*; import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.devtools.codestarts.CodestartData; +import io.quarkus.devtools.codestarts.CodestartInput; +import io.quarkus.devtools.codestarts.CodestartProject; +import io.quarkus.devtools.codestarts.Codestarts; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.buildfile.GradleBuildFilesCreator; import io.quarkus.devtools.project.codegen.ProjectGenerator; import io.quarkus.devtools.project.codegen.ProjectGeneratorRegistry; import io.quarkus.devtools.project.codegen.SourceType; +import io.quarkus.devtools.project.codegen.buildtool.GradleGenerator; import io.quarkus.devtools.project.codegen.rest.BasicRestProjectGenerator; import io.quarkus.devtools.project.extensions.ExtensionManager; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; @@ -22,6 +27,7 @@ import java.util.List; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; /** * Instances of this class are thread-safe. They create a new project extracting all the necessary properties from an instance @@ -46,6 +52,26 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws } }); + if (invocation.getValue("codestarts.enabled", false)) { + final List extensionsToAdd = computeCoordsFromQuery(invocation, extensionsQuery).stream() + .map(AppArtifactCoords::getKey) + .collect(Collectors.toList()); + + try { + final CodestartInput input = CodestartInput.builder(invocation.getPlatformDescriptor()) + .addExtensions(extensionsToAdd) + .addData(CodestartData.LegacySupport.convertFromLegacy(invocation.getValues())) + .includeExamples(invocation.getValue("codestarts.with-example-code", true)) + .build(); + final CodestartProject codestartProject = Codestarts + .prepareProject(input); + Codestarts.generateProject(codestartProject, invocation.getQuarkusProject().getProjectDirPath()); + } catch (IOException e) { + throw new QuarkusCommandException("Failed to create project", e); + } + return QuarkusCommandOutcome.success(); + } + try { String className = invocation.getStringValue(CLASS_NAME); if (className != null) { @@ -69,8 +95,8 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws //TODO ia3andy extensions should be added directly during the project generation if (invocation.getQuarkusProject().getBuildTool().equals(BuildTool.GRADLE)) { - final GradleBuildFilesCreator creator = new GradleBuildFilesCreator(invocation.getQuarkusProject()); - creator.create( + final GradleGenerator generator = new GradleGenerator(invocation.getQuarkusProject()); + generator.generate( invocation.getStringValue(PROJECT_GROUP_ID), invocation.getStringValue(PROJECT_ARTIFACT_ID), invocation.getStringValue(PROJECT_VERSION), diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java index 948404b10fd30..1664a1c5bdb8f 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java @@ -63,7 +63,7 @@ protected void addDependencyInBuildFile(AppArtifactCoords coords) throws IOExcep addDependencyInModel(getModel(), coords); } - static void addDependencyInModel(Model model, AppArtifactCoords coords) throws IOException { + public static void addDependencyInModel(Model model, AppArtifactCoords coords) throws IOException { StringBuilder newBuildContent = new StringBuilder(); readLineByLine(model.getBuildContent(), currentLine -> { newBuildContent.append(currentLine).append(System.lineSeparator()); @@ -195,7 +195,7 @@ private static void readLineByLine(String content, Consumer lineConsumer } } - static class Model { + public static class Model { private String settingsContent; private String buildContent; private Properties propertiesContent; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/GradleBuildFilesCreator.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/buildtool/GradleGenerator.java similarity index 97% rename from independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/GradleBuildFilesCreator.java rename to independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/buildtool/GradleGenerator.java index 22b4e2380282b..167818321e634 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/GradleBuildFilesCreator.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/buildtool/GradleGenerator.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.project.buildfile; +package io.quarkus.devtools.project.codegen.buildtool; import static io.quarkus.devtools.project.buildfile.AbstractGradleBuildFile.addDependencyInModel; @@ -20,7 +20,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -public final class GradleBuildFilesCreator { +public final class GradleGenerator { private static final String BUILD_GRADLE_PATH = "build.gradle"; private static final String SETTINGS_GRADLE_PATH = "settings.gradle"; @@ -29,11 +29,11 @@ public final class GradleBuildFilesCreator { private AtomicReference modelReference = new AtomicReference<>(); - public GradleBuildFilesCreator(QuarkusProject quarkusProject) { + public GradleGenerator(QuarkusProject quarkusProject) { this.quarkusProject = quarkusProject; } - public void create(String groupId, String artifactId, String version, + public void generate(String groupId, String artifactId, String version, Properties properties, List extensions) throws IOException { createSettingsContent(artifactId); createBuildContent(groupId, version); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ClassPathResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ClassPathResourceLoader.java index 9983b8eb9784a..ac2ff295fb308 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ClassPathResourceLoader.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ClassPathResourceLoader.java @@ -1,8 +1,11 @@ package io.quarkus.platform.descriptor.loader.json; import io.quarkus.platform.descriptor.ResourceInputStreamConsumer; +import io.quarkus.platform.descriptor.ResourcePathConsumer; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URL; public class ClassPathResourceLoader implements ResourceLoader { @@ -16,6 +19,21 @@ public ClassPathResourceLoader(ClassLoader cl) { this.cl = cl; } + @Override + public T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException { + final URL url = cl.getResource(name); + if (url == null) { + throw new IOException("Failed to locate " + name + " on the classpath"); + } + return ResourceLoaders.processAsPath(url, is -> { + try { + return consumer.consume(is); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + @Override public T loadResource(String name, ResourceInputStreamConsumer consumer) throws IOException { final InputStream stream = cl.getResourceAsStream(name); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/DirectoryResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/DirectoryResourceLoader.java index c42cc47894791..0091ab3e0dd8d 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/DirectoryResourceLoader.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/DirectoryResourceLoader.java @@ -1,8 +1,7 @@ package io.quarkus.platform.descriptor.loader.json; -import io.quarkus.platform.descriptor.ResourceInputStreamConsumer; +import io.quarkus.platform.descriptor.ResourcePathConsumer; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; @@ -18,16 +17,11 @@ public DirectoryResourceLoader(Path dir) { } @Override - public T loadResource(String name, ResourceInputStreamConsumer consumer) throws IOException { - Path resource = dir.resolve(name); - if (!Files.exists(resource)) { - throw new IOException("Failed to locate " + resource); - } - if (Files.isDirectory(resource)) { - throw new IOException("Can't open a stream for path pointing to directory " + resource); - } - try (InputStream is = Files.newInputStream(resource)) { - return consumer.consume(is); + public T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException { + Path path = dir.resolve(name); + if (!Files.exists(path)) { + throw new IOException("Failed to locate " + name + " dir on the classpath"); } + return consumer.consume(path); } } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoader.java index a7d67ec4a8c7a..4451305aba8dd 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoader.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoader.java @@ -1,9 +1,21 @@ package io.quarkus.platform.descriptor.loader.json; import io.quarkus.platform.descriptor.ResourceInputStreamConsumer; +import io.quarkus.platform.descriptor.ResourcePathConsumer; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; public interface ResourceLoader { - T loadResource(String name, ResourceInputStreamConsumer consumer) throws IOException; + T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException; + + default T loadResource(String name, ResourceInputStreamConsumer consumer) throws IOException { + return this.loadResourcePath(name, p -> { + try (InputStream is = Files.newInputStream(p)) { + return consumer.consume(is); + } + }); + } + } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoaders.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoaders.java new file mode 100644 index 0000000000000..d29e0b2aafa28 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ResourceLoaders.java @@ -0,0 +1,72 @@ +package io.quarkus.platform.descriptor.loader.json; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Function; + +public final class ResourceLoaders { + + private static final String FILE = "file"; + private static final String JAR = "jar"; + + private ResourceLoaders() { + } + + public static File getResourceFile(final URL url, final String name) throws IOException { + if (url == null) { + throw new IOException("Failed to locate resource " + name + " on the classpath"); + } + try { + return new File(url.toURI()); + } catch (URISyntaxException | IllegalArgumentException e) { + throw new IOException( + "There were a problem while reading the resource dir '" + name + "' on the classpath with url: '" + + url.toString() + "'"); + } + } + + // This method is copied from io.quarkus.runtime.util.ClassPathUtils + public static R processAsPath(URL url, Function function) { + if (JAR.equals(url.getProtocol())) { + final String file = url.getFile(); + final int exclam = file.lastIndexOf('!'); + final Path jar; + try { + jar = toLocalPath(exclam >= 0 ? new URL(file.substring(0, exclam)) : url); + } catch (MalformedURLException e) { + throw new RuntimeException("Failed to create a URL for '" + file.substring(0, exclam) + "'", e); + } + try (FileSystem jarFs = FileSystems.newFileSystem(jar, (ClassLoader) null)) { + Path localPath = jarFs.getPath("/"); + if (exclam >= 0) { + localPath = localPath.resolve(file.substring(exclam + 1)); + } + return function.apply(localPath); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read " + jar, e); + } + } + + if (FILE.equals(url.getProtocol())) { + return function.apply(toLocalPath(url)); + } + + throw new IllegalArgumentException("Unexpected protocol " + url.getProtocol() + " for URL " + url); + } + + private static Path toLocalPath(final URL url) { + try { + return Paths.get(url.toURI()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Failed to translate " + url + " to local path", e); + } + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ZipResourceLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ZipResourceLoader.java index 171d4be144e96..d4b372c3169a0 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ZipResourceLoader.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/platform/descriptor/loader/json/ZipResourceLoader.java @@ -1,8 +1,7 @@ package io.quarkus.platform.descriptor.loader.json; -import io.quarkus.platform.descriptor.ResourceInputStreamConsumer; +import io.quarkus.platform.descriptor.ResourcePathConsumer; import java.io.IOException; -import java.io.InputStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -17,15 +16,14 @@ public ZipResourceLoader(Path zip) { } @Override - public T loadResource(String name, ResourceInputStreamConsumer consumer) throws IOException { + public T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException { try (FileSystem fs = FileSystems.newFileSystem(zip, (ClassLoader) null)) { - final Path p = fs.getPath("/", name); - if (!Files.exists(p)) { + final Path path = fs.getPath("/", name); + if (!Files.exists(path)) { throw new IOException("Failed to locate " + name + " in " + zip); } - try (InputStream is = Files.newInputStream(p)) { - return consumer.consume(is); - } + return consumer.consume(path); } } + } diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/ProjectTestUtil.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/ProjectTestUtil.java new file mode 100644 index 0000000000000..d6b2d9503ad54 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/ProjectTestUtil.java @@ -0,0 +1,29 @@ +package io.quarkus.devtools; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; + +public final class ProjectTestUtil { + + private ProjectTestUtil() { + } + + public static void delete(final File file) throws IOException { + + if (file.exists()) { + try (Stream stream = Files.walk(file.toPath())) { + stream.sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + Assertions.assertFalse( + Files.exists(file.toPath()), "Directory still exists"); + } +} diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/CombinedQuarkusPlatformDescriptorTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/CombinedQuarkusPlatformDescriptorTest.java index f9da268b93ccc..8781df78c7dea 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/CombinedQuarkusPlatformDescriptorTest.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/CombinedQuarkusPlatformDescriptorTest.java @@ -6,7 +6,6 @@ import io.quarkus.dependencies.Category; import io.quarkus.dependencies.Extension; -import io.quarkus.devtools.commands.PlatformAwareTestBase; import io.quarkus.platform.descriptor.CombinedQuarkusPlatformDescriptor; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import java.util.List; @@ -50,9 +49,9 @@ public void testDominance() throws Exception { assertManagedDeps(combined); - assertEquals("dominating pom.xml template", combined.getTemplate("templates/basic-rest/java/pom.xml-template.ftl")); - assertEquals(defaultPlatform.getTemplate("templates/dockerfile-jvm.ftl"), - combined.getTemplate("templates/dockerfile-jvm.ftl")); + assertEquals("dominating pom.xml template", combined.getTemplate("dir/some-other-file.template")); + assertEquals(defaultPlatform.getTemplate("dir/some-file.template"), + combined.getTemplate("dir/some-file.template")); } private void assertBom(QuarkusPlatformDescriptor descriptor) { diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/PlatformAwareTestBase.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/PlatformAwareTestBase.java similarity index 97% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/PlatformAwareTestBase.java rename to independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/PlatformAwareTestBase.java index 33b1278cd484d..c04f0fc5539a2 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/PlatformAwareTestBase.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/PlatformAwareTestBase.java @@ -1,4 +1,4 @@ -package io.quarkus.devtools.commands; +package io.quarkus.test.platform.descriptor; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import io.quarkus.platform.tools.config.QuarkusPlatformConfig; diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/TestDominatingQuarkusPlatformDescriptor.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/TestDominatingQuarkusPlatformDescriptor.java index f2ba040a12c38..459ede5dc6072 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/TestDominatingQuarkusPlatformDescriptor.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/TestDominatingQuarkusPlatformDescriptor.java @@ -3,10 +3,12 @@ import static io.quarkus.test.platform.descriptor.loader.QuarkusTestPlatformDescriptorLoader.addCategory; import static io.quarkus.test.platform.descriptor.loader.QuarkusTestPlatformDescriptorLoader.addExtension; +import io.quarkus.bootstrap.model.AppArtifactCoords; import io.quarkus.dependencies.Category; import io.quarkus.dependencies.Extension; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import io.quarkus.platform.descriptor.ResourceInputStreamConsumer; +import io.quarkus.platform.descriptor.ResourcePathConsumer; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -23,8 +25,9 @@ public TestDominatingQuarkusPlatformDescriptor() { addCategory("other", "Other category", categories); addCategory("web", "Dominating Web", categories); - addExtension("io.quarkus", "quarkus-resteasy", "dominating-version", "Dominating RESTEasy", "dominating/guide", - extensions, bomDeps); + addExtension(new AppArtifactCoords("io.quarkus", "quarkus-resteasy", "dominating-version"), "Dominating RESTEasy", + "dominating/guide", + "reasteasy", extensions, bomDeps); } @Override @@ -64,7 +67,7 @@ public List getCategories() { @Override public String getTemplate(String name) { - if ("templates/basic-rest/java/pom.xml-template.ftl".equals(name)) { + if ("dir/some-other-file.template".equals(name)) { return "dominating pom.xml template"; } return null; @@ -74,4 +77,9 @@ public String getTemplate(String name) { public T loadResource(String name, ResourceInputStreamConsumer consumer) throws IOException { return null; } + + @Override + public T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException { + return null; + } } diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/loader/QuarkusTestPlatformDescriptorLoader.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/loader/QuarkusTestPlatformDescriptorLoader.java index 2f4bc35680a34..604b5659831b3 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/loader/QuarkusTestPlatformDescriptorLoader.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/test/platform/descriptor/loader/QuarkusTestPlatformDescriptorLoader.java @@ -1,18 +1,24 @@ package io.quarkus.test.platform.descriptor.loader; +import io.quarkus.bootstrap.model.AppArtifactCoords; import io.quarkus.dependencies.Category; import io.quarkus.dependencies.Extension; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import io.quarkus.platform.descriptor.ResourceInputStreamConsumer; +import io.quarkus.platform.descriptor.ResourcePathConsumer; import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoader; import io.quarkus.platform.descriptor.loader.QuarkusPlatformDescriptorLoaderContext; +import io.quarkus.platform.descriptor.loader.json.ResourceLoaders; import java.io.BufferedReader; import java.io.BufferedWriter; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; +import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -39,12 +45,15 @@ private static void addCategories() { private static void addExtensions() { addExtension("quarkus-agroal", "Agroal"); addExtension("quarkus-arc", "Arc"); + addExtension("quarkus-kotlin", "Kotlin", "url://", "kotlin"); + addExtension("quarkus-scala", "Scala", "url://", "scala"); + addExtension("quarkus-config-yaml", "Config Yaml", "url://", "config-yaml"); addExtension("quarkus-hibernate-orm-panache", "Hibernate ORM Panache"); addExtension("quarkus-hibernate-search-elasticsearch", "Elasticsearch"); addExtension("quarkus-hibernate-validator", "Hibernate Validator"); addExtension("quarkus-jdbc-postgresql", "JDBC PostreSQL"); addExtension("quarkus-jdbc-h2", "JDBC H2"); - addExtension("quarkus-resteasy", "RESTEasy", "https://quarkus.io/guides/rest-json"); + addExtension("quarkus-resteasy", "RESTEasy", "https://quarkus.io/guides/rest-json", "resteasy"); addExtension("quarkus-smallrye-reactive-messaging", "SmallRye Reactive Messaging"); addExtension("quarkus-smallrye-reactive-streams-operators", "SmallRye Reactive Streams Operators"); @@ -67,21 +76,26 @@ private static void addExtension(String artifactId, String name) { } private static void addExtension(String artifactId, String name, String guide) { - addExtension("io.quarkus", artifactId, quarkusVersion, name, guide); + addExtension(artifactId, name, guide, null); } - private static void addExtension(String groupId, String artifactId, String version, String name, String guide) { - addExtension(groupId, artifactId, version, name, guide, extensions, bomDeps); + private static void addExtension(String artifactId, String name, String guide, String codestart) { + addExtension(new AppArtifactCoords("io.quarkus", artifactId, quarkusVersion), name, guide, codestart); } - public static void addExtension(String groupId, String artifactId, String version, String name, String guide, + private static void addExtension(AppArtifactCoords coords, String name, String guide, String codestart) { + addExtension(coords, name, guide, codestart, extensions, bomDeps); + } + + public static void addExtension(AppArtifactCoords coords, String name, String guide, String codestart, List extensions, List bomDeps) { - extensions.add(new Extension(groupId, artifactId, version).setName(name).setGuide(guide)); + extensions.add(new Extension(coords.getGroupId(), coords.getArtifactId(), coords.getVersion()).setName(name) + .setGuide(guide).setCodestart(codestart)); final Dependency d = new Dependency(); - d.setGroupId(groupId); - d.setArtifactId(artifactId); - d.setVersion(version); + d.setGroupId(coords.getGroupId()); + d.setArtifactId(coords.getArtifactId()); + d.setVersion(coords.getVersion()); bomDeps.add(d); } @@ -174,6 +188,11 @@ public T loadResource(String name, ResourceInputStreamConsumer consumer) return loadStaticResource(name, consumer); } + @Override + public T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException { + return loadStaticResourcePath(name, consumer); + } + @Override public List getCategories() { return categories; @@ -192,4 +211,13 @@ private static T loadStaticResource(String name, ResourceInputStreamConsumer is.close(); } } + + private static T loadStaticResourcePath(String name, ResourcePathConsumer consumer) throws IOException { + final URL url = Thread.currentThread().getContextClassLoader().getResource(name); + final File file = ResourceLoaders.getResourceFile(url, name); + if (!Files.exists(file.toPath())) { + throw new IOException("Failed to locate resource " + name + " on the classpath"); + } + return consumer.consume(file.toPath()); + } } diff --git a/independent-projects/tools/devtools-common/src/test/resources/dir/some-file.template b/independent-projects/tools/devtools-common/src/test/resources/dir/some-file.template new file mode 100644 index 0000000000000..93d00322a11cc --- /dev/null +++ b/independent-projects/tools/devtools-common/src/test/resources/dir/some-file.template @@ -0,0 +1,3 @@ +Humble smack Shirelings tears baby usurper mouthful. Embrace fresh burned oil Wargs regurgitation unheard-of. Brandybucks puppet vines boats familiar prizewinners tunnel leaving riverbank welcome threw 60? Durin's certain washing crawled children? Pouf Dwarvish elected fruity writing. Laughing reserves hinges Barad-dur either troublemakers saw pleased thereof set weren't penny. Hear my voice. Come back to the light. Slight keyhole strong Shirkers 3434 promised? +Water pointy-ear Saruman notion deceived go outer river salted unpredictable slowed. Galadriel strawberries scum surety bolted suffer delaying him lidless achieving killing. I have no memory of this place. Arguing Lake-men scale pon tree falling. Slugs when presses wasteland cousin sing pool Éomund. Resown how shaft wheeled checked dream later Rohan's ago. Famished accustomed you'd Rohan save thicket better plan knife-work becoming ten. Swim solid Ere? Opened clothes filleting devilry struck incident Southrons. +Dancing there wet since sooner contract granny Dunharrow father bless. Easy pipe-weed sweeter talks Kili? Fried raw perfected Dimholt together taught toad child. Avenged cheated fill sigh choking deep biding drown Rabble-rousers very. Smirking adventure exchanged bodyguard Dî courteous. Tries fact merchants Wargs badly. All right, then. Keep your secrets. Fours craft new announce faint Dale beside relic advantage travel cook long-forgotten? Short entire weapons knows Westfold ordered birdsong mustn't? Trousers glory consequence possible poured claim Baggins leg telling squeal. \ No newline at end of file diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java index 1c3eda6389a5c..f867b080c8561 100644 --- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java +++ b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java @@ -26,6 +26,8 @@ public class Extension implements Serializable { public static final String MD_SHORT_NAME = "short-name"; + public static final String MD_CODESTART = "codestart"; + public static final String MD_GUIDE = "guide"; /** Key used for keywords in metadata **/ @@ -264,6 +266,15 @@ public Extension setShortName(String shortName) { return this; } + public String getCodestart() { + return (String) getMetadata().get(MD_CODESTART); + } + + public Extension setCodestart(String codestart) { + getMetadata().put(MD_CODESTART, codestart); + return this; + } + public boolean isUnlisted() { Object val = getMetadata().get(MD_UNLISTED); if (val == null) { diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/CombinedQuarkusPlatformDescriptor.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/CombinedQuarkusPlatformDescriptor.java index 55e9f2d8a7bf0..088a93b9159fb 100644 --- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/CombinedQuarkusPlatformDescriptor.java +++ b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/CombinedQuarkusPlatformDescriptor.java @@ -153,6 +153,18 @@ public T loadResource(String name, ResourceInputStreamConsumer consumer) throw new IOException("Failed to locate resource " + name); } + @Override + public T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException { + for (QuarkusPlatformDescriptor platform : platforms) { + try { + return platform.loadResourcePath(name, consumer); + } catch (IOException e) { + // ignore + } + } + throw new IOException("Failed to locate resource " + name); + } + private static class DepKey { final String groupId; final String artifactId; diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/QuarkusPlatformDescriptor.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/QuarkusPlatformDescriptor.java index f13d977975e2b..8bb5d6f3fe5ac 100644 --- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/QuarkusPlatformDescriptor.java +++ b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/QuarkusPlatformDescriptor.java @@ -26,6 +26,8 @@ public interface QuarkusPlatformDescriptor { T loadResource(String name, ResourceInputStreamConsumer consumer) throws IOException; + T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException; + default String gav() { return String.format("%s:%s:%s", getBomGroupId(), getBomArtifactId(), getBomVersion()); } diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourcePathConsumer.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourcePathConsumer.java new file mode 100644 index 0000000000000..88ccd84c23858 --- /dev/null +++ b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/platform/descriptor/ResourcePathConsumer.java @@ -0,0 +1,9 @@ +package io.quarkus.platform.descriptor; + +import java.io.IOException; +import java.nio.file.Path; + +public interface ResourcePathConsumer { + + T consume(Path is) throws IOException; +} diff --git a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java index 1e607484cc4a0..b42ba7f5be6fe 100644 --- a/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java +++ b/independent-projects/tools/platform-descriptor-resolver-json/src/test/java/io/quarkus/platform/descriptor/resolver/json/test/TestJsonPlatformDescriptorLoader.java @@ -7,6 +7,7 @@ import io.quarkus.dependencies.Extension; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import io.quarkus.platform.descriptor.ResourceInputStreamConsumer; +import io.quarkus.platform.descriptor.ResourcePathConsumer; import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoader; import io.quarkus.platform.descriptor.loader.json.QuarkusJsonPlatformDescriptorLoaderContext; import java.io.IOException; @@ -80,6 +81,11 @@ public String getTemplate(String name) { public T loadResource(String name, ResourceInputStreamConsumer consumer) throws IOException { return null; } + + @Override + public T loadResourcePath(String name, ResourcePathConsumer consumer) throws IOException { + return null; + } }; } diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 784a9e333fd1a..fb79747429810 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -52,9 +52,13 @@ 1.6.8 999-SNAPSHOT 3.0.11.Final + 2.10.4 + 14 + 2.6 platform-descriptor-api + codestarts devtools-common utilities platform-descriptor-resolver-json @@ -76,11 +80,26 @@ quarkus-bootstrap-maven-resolver ${quarkus.version} + + io.quarkus + quarkus-bootstrap-app-model + ${quarkus.version} + io.quarkus quarkus-devtools-utilities ${project.version} + + io.quarkus + quarkus-devtools-codestarts + ${project.version} + + + io.quarkus.qute + qute-generator + ${project.version} + io.quarkus quarkus-devtools-common @@ -102,6 +121,26 @@ + + commons-io + commons-io + ${commons-io.version} + + + io.fabric8 + maven-model-helper + ${maven-model-helper.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + org.apache.maven maven-plugin-api diff --git a/integration-tests/devtools/pom.xml b/integration-tests/devtools/pom.xml new file mode 100644 index 0000000000000..a2f5563c8ca43 --- /dev/null +++ b/integration-tests/devtools/pom.xml @@ -0,0 +1,65 @@ + + + + io.quarkus + quarkus-integration-tests-parent + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-devtools + Quarkus - Integration Tests - Devtools + + + + + io.quarkus + quarkus-devtools-common + + + io.quarkus + quarkus-platform-descriptor-json + + + io.quarkus + quarkus-platform-descriptor-resolver-json + test + + + io.quarkus + quarkus-junit5-internal + test + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/PlatformAwareTestBase.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/PlatformAwareTestBase.java new file mode 100644 index 0000000000000..cf374e267bde1 --- /dev/null +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/PlatformAwareTestBase.java @@ -0,0 +1,62 @@ +package io.quarkus.devtools; + +import java.io.IOException; +import java.util.Properties; + +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import io.quarkus.platform.descriptor.resolver.json.QuarkusJsonPlatformDescriptorResolver; + +public class PlatformAwareTestBase { + + private QuarkusPlatformDescriptor platformDescr; + private Properties quarkusProps; + private String pluginGroupId; + private String pluginArtifactId; + private String pluginVersion; + + protected QuarkusPlatformDescriptor getPlatformDescriptor() { + return platformDescr == null + ? platformDescr = QuarkusJsonPlatformDescriptorResolver.newInstance().resolveBundled() + : platformDescr; + } + + private Properties getQuarkusProperties() { + if (quarkusProps == null) { + try { + quarkusProps = getPlatformDescriptor().loadResource("quarkus.properties", is -> { + final Properties props = new Properties(); + props.load(is); + return props; + }); + } catch (IOException e) { + throw new IllegalStateException("Failed to load quarkus.properties", e); + } + } + return quarkusProps; + } + + protected String getPluginGroupId() { + return pluginGroupId == null ? pluginGroupId = getQuarkusProperties().getProperty("plugin-groupId") : pluginGroupId; + } + + protected String getPluginArtifactId() { + return pluginArtifactId == null ? pluginArtifactId = getQuarkusProperties().getProperty("plugin-artifactId") + : pluginArtifactId; + } + + protected String getPluginVersion() { + return pluginVersion == null ? pluginVersion = getQuarkusProperties().getProperty("plugin-version") : pluginVersion; + } + + protected String getBomGroupId() { + return getPlatformDescriptor().getBomGroupId(); + } + + protected String getBomArtifactId() { + return getPlatformDescriptor().getBomArtifactId(); + } + + protected String getBomVersion() { + return getPlatformDescriptor().getBomVersion(); + } +} diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/ProjectTestUtil.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/ProjectTestUtil.java new file mode 100644 index 0000000000000..241ab1c7fb4b9 --- /dev/null +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/ProjectTestUtil.java @@ -0,0 +1,30 @@ +package io.quarkus.devtools; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Assertions; + +public final class ProjectTestUtil { + + private ProjectTestUtil() { + } + + public static void delete(final File file) throws IOException { + + if (file.exists()) { + try (Stream stream = Files.walk(file.toPath())) { + stream.sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + Assertions.assertFalse( + Files.exists(file.toPath()), "Directory still exists"); + } +} diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/CodestartProjectTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/CodestartProjectTest.java new file mode 100644 index 0000000000000..10a3b73b13db2 --- /dev/null +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/CodestartProjectTest.java @@ -0,0 +1,303 @@ +package io.quarkus.devtools.codestarts; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.ProjectTestUtil; + +import static org.assertj.core.api.Assertions.assertThat; + +class CodestartProjectTest extends PlatformAwareTestBase { + + private static final Path projectPath = Paths.get("target/codestarts-test"); + + @BeforeAll + static void setUp() throws IOException { + ProjectTestUtil.delete(projectPath.toFile()); + } + + private Map getTestInputData() { + return getTestInputData(null); + } + + private Map getTestInputData(final Map override) { + final HashMap data = new HashMap<>(); + data.put("project.version", "1.0.0-codestart"); + data.put("quarkus.platform.group-id", getPlatformDescriptor().getBomGroupId()); + data.put("quarkus.platform.artifact-id", getPlatformDescriptor().getBomArtifactId()); + data.put("quarkus.platform.version", "1.6.0.Final"); + data.put("quarkus.plugin.group-id", "io.quarkus"); + data.put("quarkus.plugin.artifact-id", "quarkus-maven-plugin"); + data.put("quarkus.plugin.version", "1.6.0.Final"); + if (override != null) + data.putAll(override); + return data; + } + + @Test + void generateCodestartProjectEmpty() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("empty"); + Codestarts.generateProject(codestartProject, projectDir); + + checkMaven(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkNoExample(projectDir); + } + + + @Test + void generateCodestartProjectEmptyWithExamples() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("empty-examples"); + Codestarts.generateProject(codestartProject, projectDir); + + checkMaven(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/java/org/acme/commandmode/GreetingMain.java")).exists(); + } + + + + @Test + void generateCodestartProjectMavenResteasyJava() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-resteasy")) + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("maven-resteasy-java"); + Codestarts.generateProject(codestartProject, projectDir); + + checkMaven(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/java/org/acme/resteasy/ExampleResource.java")).exists(); + } + + @Test + void generateCodestartProjectMavenResteasyKotlin() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-resteasy")) + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-kotlin")) + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("maven-resteasy-kotlin"); + Codestarts.generateProject(codestartProject, projectDir); + + checkMaven(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/kotlin/org/acme/resteasy/ExampleResource.kt")).exists(); + } + + @Test + void generateCodestartProjectMavenResteasyScala() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-resteasy")) + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-scala")) + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("maven-resteasy-scala"); + Codestarts.generateProject(codestartProject, projectDir); + + checkMaven(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/scala/org/acme/resteasy/ExampleResource.scala")).exists(); + } + + @Test + void generateCodestartProjectGradleResteasyJava() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-resteasy")) + .addData(getTestInputData()) + .putData("buildtool.name", "gradle") + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("gradle-resteasy-java"); + Codestarts.generateProject(codestartProject, projectDir); + + checkGradle(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/java/org/acme/resteasy/ExampleResource.java")).exists(); + } + + @Test + void generateCodestartProjectGradleResteasyKotlin() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-resteasy")) + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-kotlin")) + .putData("buildtool.name", "gradle") + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("gradle-resteasy-kotlin"); + Codestarts.generateProject(codestartProject, projectDir); + + checkGradle(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/kotlin/org/acme/resteasy/ExampleResource.kt")).exists(); + } + + @Test + void generateCodestartProjectGradleResteasyScala() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-resteasy")) + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-scala")) + .addData(getTestInputData()) + .putData("buildtool.name", "gradle") + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("gradle-resteasy-scala"); + Codestarts.generateProject(codestartProject, projectDir); + + checkGradle(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/scala/org/acme/resteasy/ExampleResource.scala")).exists(); + } + + @Test + void generateCodestartProjectMavenOptaplannerJava() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addCodestart("optaplanner") + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("maven-optaplanner-java"); + Codestarts.generateProject(codestartProject, projectDir); + + checkMaven(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/java/org/acme/optaplanner/rest/RoomResource.java")).exists(); + } + + @Test + void generateCodestartProjectMavenOptaplannerJavaYamlConfig() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-config-yaml")) + .addCodestart("optaplanner") + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("maven-optaplanner-java-yaml-config"); + Codestarts.generateProject(codestartProject, projectDir); + + checkMaven(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigYaml(projectDir); + + assertThat(projectDir.resolve("src/main/java/org/acme/optaplanner/rest/RoomResource.java")).exists(); + } + + @Test + void generateCodestartProjectQute() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-qute")) + .addCodestart("qute") + .addData(getTestInputData()) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + final Path projectDir = projectPath.resolve("maven-qute"); + Codestarts.generateProject(codestartProject, projectDir); + + checkMaven(projectDir); + checkReadme(projectDir); + checkDockerfiles(projectDir); + checkConfigProperties(projectDir); + + assertThat(projectDir.resolve("src/main/java/org/acme/qute/Item.java")).exists(); + } + + private void checkNoExample(Path projectDir) { + assertThat(projectDir.resolve("src/main/java")).doesNotExist(); + assertThat(projectDir.resolve("src/main/kotlin")).doesNotExist(); + assertThat(projectDir.resolve("src/main/scala")).doesNotExist(); + } + + private void checkDockerfiles(Path projectDir) { + assertThat(projectDir.resolve(".dockerignore")).exists(); + assertThat(projectDir.resolve("src/main/docker/Dockerfile.jvm")).exists(); + assertThat(projectDir.resolve("src/main/docker/Dockerfile.native")).exists(); + assertThat(projectDir.resolve("src/main/docker/Dockerfile.fast-jar")).exists(); + } + + private void checkConfigProperties(Path projectDir) { + assertThat(projectDir.resolve("src/main/resources/application.yml")).doesNotExist(); + assertThat(projectDir.resolve("src/main/resources/application.properties")).exists(); + } + + private void checkConfigYaml(Path projectDir) { + assertThat(projectDir.resolve("src/main/resources/application.yml")).exists(); + assertThat(projectDir.resolve("src/main/resources/application.properties")).doesNotExist(); + } + + private void checkReadme(Path projectDir) { + assertThat(projectDir.resolve("README.md")).exists(); + assertThat(projectDir.resolve(".gitignore")).exists(); + } + + private void checkMaven(Path projectDir) { + assertThat(projectDir.resolve("pom.xml")).exists(); + assertThat(projectDir.resolve("build.gradle")).doesNotExist(); + assertThat(projectDir.resolve("gradle.properties")).doesNotExist(); + assertThat(projectDir.resolve("settings.properties")).doesNotExist(); + } + + private void checkGradle(Path projectDir) { + assertThat(projectDir.resolve("pom.xml")).doesNotExist(); + assertThat(projectDir.resolve("build.gradle")).exists(); + assertThat(projectDir.resolve("gradle.properties")).exists(); + assertThat(projectDir.resolve("settings.gradle")).exists(); + } +} diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/CodestartsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/CodestartsTest.java new file mode 100644 index 0000000000000..1e75226821c11 --- /dev/null +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/codestarts/CodestartsTest.java @@ -0,0 +1,119 @@ +package io.quarkus.devtools.codestarts; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.codestarts.CodestartSpec.Type; + +class CodestartsTest extends PlatformAwareTestBase { + + private final Path projectPath = Paths.get("target/codestarts-test"); + + @BeforeEach + void setUp() throws IOException { + ProjectTestUtil.delete(projectPath.toFile()); + } + + @Test + void loadBundledCodestartsTest() throws IOException { + final Collection codestarts = CodestartLoader + .loadBundledCodestarts(CodestartInput.builder(getPlatformDescriptor()).build()); + assertThat(codestarts).hasSize(10); + } + + @Test + void prepareProjectTestEmpty() throws IOException { + final CodestartProject codestartProject = Codestarts + .prepareProject(CodestartInput.builder(getPlatformDescriptor()).build()); + assertThat(codestartProject.getRequiredCodestart(Type.PROJECT)).extracting(Codestart::getResourceName) + .isEqualTo("bundled-codestarts/project/quarkus"); + assertThat(codestartProject.getRequiredCodestart(Type.BUILDTOOL)).extracting(Codestart::getResourceName) + .isEqualTo("bundled-codestarts/buildtool/maven"); + assertThat(codestartProject.getRequiredCodestart(Type.CONFIG)).extracting(Codestart::getResourceName) + .isEqualTo("bundled-codestarts/config/properties"); + assertThat(codestartProject.getRequiredCodestart(Type.LANGUAGE)).extracting(Codestart::getResourceName) + .isEqualTo("bundled-codestarts/language/java"); + assertThat(codestartProject.getBaseCodestarts()).hasSize(4); + assertThat(codestartProject.getExtraCodestarts()).extracting(Codestart::getResourceName) + .containsExactlyInAnyOrder("bundled-codestarts/tooling/dockerfiles"); + } + + @Test + void prepareProjectTestGradle() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .putData("buildtool.name", "gradle") + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + assertThat(codestartProject.getRequiredCodestart(Type.BUILDTOOL)).extracting(Codestart::getResourceName) + .isEqualTo("bundled-codestarts/buildtool/gradle"); + } + + @Test + void prepareProjectTestKotlin() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-kotlin")) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + assertThat(codestartProject.getRequiredCodestart(Type.LANGUAGE)).extracting(Codestart::getResourceName) + .isEqualTo("bundled-codestarts/language/kotlin"); + } + + @Test + void prepareProjectTestScala() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-scala")) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + assertThat(codestartProject.getRequiredCodestart(Type.LANGUAGE)).extracting(Codestart::getResourceName) + .isEqualTo("bundled-codestarts/language/scala"); + } + + @Test + void prepareProjectTestConfigYaml() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-config-yaml")) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + assertThat(codestartProject.getRequiredCodestart(Type.CONFIG)).extracting(Codestart::getResourceName) + .isEqualTo("bundled-codestarts/config/yaml"); + } + + @Test + void prepareProjectTestResteasy() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .addExtension(AppArtifactKey.fromString("io.quarkus:quarkus-resteasy")) + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + assertThat(codestartProject.getBaseCodestarts()).extracting(Codestart::getResourceName) + .contains("bundled-codestarts/config/properties"); + assertThat(codestartProject.getExtraCodestarts()).extracting(Codestart::getResourceName) + .containsExactlyInAnyOrder("bundled-codestarts/tooling/dockerfiles", "codestarts/resteasy-example"); + } + + @Test + void prepareProjectTestCommandMode() throws IOException { + final CodestartInput input = CodestartInput.builder(getPlatformDescriptor()) + .includeExamples() + .build(); + final CodestartProject codestartProject = Codestarts.prepareProject(input); + assertThat(codestartProject.getBaseCodestarts()).extracting(Codestart::getResourceName) + .contains("bundled-codestarts/config/properties"); + assertThat(codestartProject.getExtraCodestarts()).extracting(Codestart::getResourceName) + .containsExactlyInAnyOrder("bundled-codestarts/tooling/dockerfiles", + "bundled-codestarts/example/commandmode-example"); + } +} diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java similarity index 91% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java index 32c0e4c1aba88..c6cb81792367f 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractAddExtensionsTest.java @@ -2,16 +2,19 @@ import static java.util.Arrays.asList; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; + abstract class AbstractAddExtensionsTest extends PlatformAwareTestBase { private final Path projectPath = Paths.get("target/extensions-test"); @@ -36,12 +39,12 @@ void addSomeValidExtensions() throws Exception { @Test void testPartialMatches() throws Exception { createProject(); - addExtensions(asList("orm-pana", "jdbc-postgre", "arc")); + addExtensions(asList("mongodb-pana", "hibernate-val", "arc")); final T project = readProject(); hasDependency(project, "quarkus-arc"); - hasDependency(project, "quarkus-hibernate-orm-panache"); - hasDependency(project, "quarkus-jdbc-postgresql"); + hasDependency(project, "quarkus-mongodb-panache"); + hasDependency(project, "quarkus-hibernate-validator"); } @Test @@ -61,10 +64,11 @@ void testRegexpMatches() throws Exception { hasDependency(project, "quarkus-smallrye-reactive-messaging-kafka"); hasDependency(project, "quarkus-smallrye-health"); hasDependency(project, "quarkus-smallrye-openapi"); + hasDependency(project, "quarkus-smallrye-graphql"); hasDependency(project, "quarkus-smallrye-jwt"); hasDependency(project, "quarkus-smallrye-context-propagation"); - hasDependency(project, "quarkus-smallrye-reactive-type-converters"); hasDependency(project, "quarkus-smallrye-reactive-messaging-amqp"); + hasDependency(project, "quarkus-smallrye-reactive-messaging-mqtt"); hasDependency(project, "quarkus-smallrye-fault-tolerance"); } @@ -206,10 +210,13 @@ void testVertx() throws Exception { void testVertxWithDot() throws Exception { createProject(); - addExtensions(Collections.singletonList("vert.x")); + // It's not possible anymore to install vert.x this way since there are more than one matching extension + final QuarkusCommandOutcome result = addExtensions(Collections.singletonList("vert.x")); + Assertions.assertFalse(result.isSuccess()); + Assertions.assertFalse(result.valueIs(AddExtensions.OUTCOME_UPDATED, true)); final T project = readProject(); - hasDependency(project, "quarkus-vertx"); + doesNotHaveDependency(project, "quarkus-vertx"); } private void hasDependency(T project, String artifactId) { diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AbstractRemoveExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractRemoveExtensionsTest.java similarity index 92% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AbstractRemoveExtensionsTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractRemoveExtensionsTest.java index 9be4524fa0508..4507624cc19ff 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AbstractRemoveExtensionsTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AbstractRemoveExtensionsTest.java @@ -2,15 +2,18 @@ import static java.util.Arrays.asList; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; + abstract class AbstractRemoveExtensionsTest extends PlatformAwareTestBase { private final Path projectPath = Paths.get("target/extensions-test"); @@ -44,19 +47,19 @@ void removeSomeValidExtensions() throws Exception { @Test void testPartialMatches() throws Exception { createProject(); - List extensions = asList("orm-pana", "jdbc-postgre", "arc"); + List extensions = asList("mongodb-pana", "hibernate-val", "arc"); addExtensions(extensions); final T project = readProject(); hasDependency(project, "quarkus-arc"); - hasDependency(project, "quarkus-hibernate-orm-panache"); - hasDependency(project, "quarkus-jdbc-postgresql"); + hasDependency(project, "quarkus-mongodb-panache"); + hasDependency(project, "quarkus-hibernate-validator"); removeExtensions(extensions); final T projectAfter = readProject(); hasNoDependency(projectAfter, "quarkus-arc"); - hasNoDependency(projectAfter, "quarkus-hibernate-orm-panache"); - hasNoDependency(projectAfter, "quarkus-jdbc-postgresql"); + hasNoDependency(projectAfter, "quarkus-mongodb-panache"); + hasNoDependency(projectAfter, "quarkus-hibernate-validator"); } @Test diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java similarity index 97% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java index f0e2c3ea201e3..21ebd05128860 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddGradleExtensionsTest.java @@ -1,11 +1,5 @@ package io.quarkus.devtools.commands; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; -import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.QuarkusProject; -import io.quarkus.devtools.project.buildfile.AbstractGradleBuildFile; -import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -14,13 +8,22 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; + import org.apache.maven.model.Dependency; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.buildfile.AbstractGradleBuildFile; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; + class AddGradleExtensionsTest extends AbstractAddExtensionsTest> { @Override protected List createProject() throws IOException, QuarkusCommandException { - CreateProjectTest.delete(getProjectPath().toFile()); + ProjectTestUtil.delete(getProjectPath().toFile()); new CreateProject(getProjectPath(), getPlatformDescriptor()) .buildTool(BuildTool.GRADLE) .groupId("org.acme") diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java similarity index 95% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java index 904777bf8917e..e14cbb7e0b701 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/AddMavenExtensionsTest.java @@ -1,23 +1,26 @@ package io.quarkus.devtools.commands; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; -import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.QuarkusProject; -import io.quarkus.maven.utilities.MojoUtils; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Objects; + import org.apache.maven.model.Model; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.maven.utilities.MojoUtils; + class AddMavenExtensionsTest extends AbstractAddExtensionsTest { @Override protected Model createProject() throws IOException, QuarkusCommandException { final File pom = getProjectPath().resolve("pom.xml").toFile(); - CreateProjectTest.delete(getProjectPath().toFile()); + ProjectTestUtil.delete(getProjectPath().toFile()); new CreateProject(getProjectPath(), getPlatformDescriptor()) .buildTool(BuildTool.MAVEN) .groupId("org.acme") diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java similarity index 89% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java index f7a7c993c4821..cad083d7e5be7 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateProjectTest.java @@ -6,17 +6,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; -import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.codegen.writer.FileProjectWriter; -import io.quarkus.maven.utilities.MojoUtils; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; @@ -24,18 +17,26 @@ import java.util.concurrent.Executors; import java.util.stream.Collectors; import java.util.stream.IntStream; -import java.util.stream.Stream; + import org.apache.maven.model.Model; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.codegen.writer.FileProjectWriter; +import io.quarkus.maven.utilities.MojoUtils; + public class CreateProjectTest extends PlatformAwareTestBase { @Test public void create() throws Exception { final File file = new File("target/basic-rest"); - delete(file); + ProjectTestUtil.delete(file); createProject(file, "io.quarkus", "basic-rest", "1.0.0-SNAPSHOT"); final File gitignore = new File(file, ".gitignore"); @@ -47,7 +48,7 @@ public void create() throws Exception { @Test public void createGradle() throws Exception { final File file = new File("target/basic-rest-gradle"); - delete(file); + ProjectTestUtil.delete(file); createProject(BuildTool.GRADLE, file, "io.quarkus", "basic-rest", "1.0.0-SNAPSHOT"); final File gitignore = new File(file, ".gitignore"); @@ -64,7 +65,7 @@ public void createGradle() throws Exception { @Test public void createOnTopOfExisting() throws Exception { final File testDir = new File("target/existing"); - delete(testDir); + ProjectTestUtil.delete(testDir); testDir.mkdirs(); Model model = new Model(); @@ -110,20 +111,6 @@ void createMultipleTimes() throws InterruptedException { latch.await(); } - public static void delete(final File file) throws IOException { - - if (file.exists()) { - try (Stream stream = Files.walk(file.toPath())) { - stream.sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - } - - Assertions.assertFalse( - Files.exists(file.toPath()), "Directory still exists"); - } - private void createProject(final File file, String groupId, String artifactId, String version) throws QuarkusCommandException { createProject(BuildTool.MAVEN, file, groupId, artifactId, version); diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java similarity index 95% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java index 15dde3f7a6237..7f12f0163fe0e 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java @@ -6,12 +6,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.quarkus.bootstrap.model.AppArtifactCoords; -import io.quarkus.bootstrap.model.AppArtifactKey; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.project.QuarkusProject; -import io.quarkus.maven.utilities.MojoUtils; -import io.quarkus.maven.utilities.QuarkusDependencyPredicate; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -23,10 +17,20 @@ import java.util.HashSet; import java.util.Map; import java.util.function.Function; + import org.apache.maven.model.Model; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.maven.utilities.QuarkusDependencyPredicate; + public class ListExtensionsTest extends PlatformAwareTestBase { @Test @@ -95,10 +99,10 @@ public void listWithoutBom() throws Exception { final String output = baos.toString("UTF-8"); boolean checkGuideInLineAfter = false; for (String line : output.split("\r?\n")) { - if (line.contains(" Agroal ")) { + if (line.contains("agroal")) { assertTrue(line.startsWith("default"), "Agroal should list as being default: " + line); agroal = true; - } else if (line.contains(" RESTEasy ")) { + } else if (line.contains("quarkus-resteasy ")) { assertTrue(line.startsWith("custom*"), "RESTEasy should list as being custom*: " + line); assertTrue( line.endsWith( @@ -106,13 +110,13 @@ public void listWithoutBom() throws Exception { "RESTEasy should list as being custom*: " + line); resteasy = true; checkGuideInLineAfter = true; - } else if (line.contains("quarkus-hibernate-orm-panache")) { + } else if (line.contains("quarkus-hibernate-orm-panache ")) { assertTrue(line.startsWith("custom"), "Panache should list as being custom: " + line); assertTrue( line.endsWith(String.format("%-25s", getPluginVersion())), "Panache should list as being custom*: " + line); panache = true; - } else if (line.contains(" Hibernate Validator ")) { + } else if (line.contains("hibernate-validator")) { assertTrue(line.startsWith(" "), "Hibernate Validator should not list as anything: " + line); hibernateValidator = true; } else if (checkGuideInLineAfter) { @@ -124,7 +128,10 @@ public void listWithoutBom() throws Exception { } } - assertTrue(agroal && resteasy && hibernateValidator && panache); + assertTrue(agroal); + assertTrue(resteasy); + assertTrue(hibernateValidator); + assertTrue(panache); } @Test @@ -185,7 +192,7 @@ private void addExtensions(QuarkusProject quarkusProject, String... extensions) } private QuarkusProject createNewProject(final File pom) throws IOException, QuarkusCommandException { - CreateProjectTest.delete(pom.getParentFile()); + ProjectTestUtil.delete(pom.getParentFile()); final Path projectDirPath = pom.getParentFile().toPath(); new CreateProject(projectDirPath, getPlatformDescriptor()) .groupId("org.acme") diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java similarity index 96% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java index 963af786163e6..f9fcd1689941e 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveGradleExtensionsTest.java @@ -1,17 +1,20 @@ package io.quarkus.devtools.commands; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; -import io.quarkus.devtools.project.BuildTool; -import io.quarkus.devtools.project.QuarkusProject; -import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.List; + import org.junit.jupiter.api.Disabled; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; + class RemoveGradleExtensionsTest extends AbstractRemoveExtensionsTest> { @Disabled @@ -21,7 +24,7 @@ void addExtensionTwiceInTwoBatches() throws IOException { @Override protected List createProject() throws IOException, QuarkusCommandException { - CreateProjectTest.delete(getProjectPath().toFile()); + ProjectTestUtil.delete(getProjectPath().toFile()); new CreateProject(getProjectPath(), getPlatformDescriptor()) .buildTool(BuildTool.GRADLE) .groupId("org.acme") diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java similarity index 95% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java index febaba8565c34..33ded3e7e496a 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/RemoveMavenExtensionsTest.java @@ -1,22 +1,25 @@ package io.quarkus.devtools.commands; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; -import io.quarkus.devtools.project.QuarkusProject; -import io.quarkus.maven.utilities.MojoUtils; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Objects; + import org.apache.maven.model.Model; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.maven.utilities.MojoUtils; + class RemoveMavenExtensionsTest extends AbstractRemoveExtensionsTest { @Override protected Model createProject() throws IOException, QuarkusCommandException { final File pom = getProjectPath().resolve("pom.xml").toFile(); - CreateProjectTest.delete(getProjectPath().toFile()); + ProjectTestUtil.delete(getProjectPath().toFile()); new CreateProject(getProjectPath(), getPlatformDescriptor()) .groupId("org.acme") .artifactId("add-maven-extension-test") diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java similarity index 99% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java index ab9135375aa1c..2b6f9b51bcabe 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java @@ -3,13 +3,15 @@ import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.select; import static java.util.Arrays.asList; -import io.quarkus.dependencies.Extension; -import io.quarkus.devtools.commands.data.SelectionResult; import java.util.Collections; import java.util.List; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.dependencies.Extension; +import io.quarkus.devtools.commands.data.SelectionResult; + class QuarkusCommandHandlersTest { @Test diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java similarity index 99% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java index fbdf838568ac2..a745b1cdb3711 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/codegen/rest/BasicRestProjectGeneratorTest.java @@ -16,13 +16,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.quarkus.bootstrap.util.IoUtils; -import io.quarkus.devtools.commands.PlatformAwareTestBase; -import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; -import io.quarkus.devtools.project.QuarkusProject; -import io.quarkus.devtools.project.codegen.SourceType; -import io.quarkus.devtools.project.codegen.writer.ProjectWriter; -import io.quarkus.maven.utilities.MojoUtils; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; @@ -34,11 +27,20 @@ import java.util.concurrent.Executors; import java.util.stream.Collectors; import java.util.stream.IntStream; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.codegen.SourceType; +import io.quarkus.devtools.project.codegen.writer.ProjectWriter; +import io.quarkus.maven.utilities.MojoUtils; + class BasicRestProjectGeneratorTest extends PlatformAwareTestBase { private final Map basicProjectContext = new HashMap<>(); diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java similarity index 95% rename from independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java rename to integration-tests/devtools/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java index c3d57ffc3b799..40b146208ddb5 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/project/compress/QuarkusProjectCompressTest.java @@ -6,11 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.quarkus.devtools.commands.CreateProject; -import io.quarkus.devtools.commands.CreateProjectTest; -import io.quarkus.devtools.commands.PlatformAwareTestBase; -import io.quarkus.devtools.commands.data.QuarkusCommandException; -import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -20,15 +15,22 @@ import java.nio.file.Paths; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; + import org.junit.jupiter.api.Test; +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.commands.CreateProject; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; + class QuarkusProjectCompressTest extends PlatformAwareTestBase { @Test public void createZip() throws Exception { // Given a Quarkus project final Path testDir = Paths.get("target/zip"); - CreateProjectTest.delete(testDir.toFile()); + ProjectTestUtil.delete(testDir.toFile()); testDir.toFile().mkdirs(); Path zip = testDir.resolve("project.zip"); @@ -49,7 +51,7 @@ public void createZip() throws Exception { public void createZipWithParentFolder() throws Exception { // Given a Quarkus project final Path testDir = Paths.get("target/zip"); - CreateProjectTest.delete(testDir.toFile()); + ProjectTestUtil.delete(testDir.toFile()); testDir.toFile().mkdirs(); Path zip = testDir.resolve("project.zip"); diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 8ba2cdf4cf0b3..63d3e929274d7 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -28,6 +28,7 @@ hibernate-validator common-jpa-entities infinispan-client + devtools gradle main kafka diff --git a/pom.xml b/pom.xml index f03f7ff88b27c..3098e112d8c46 100644 --- a/pom.xml +++ b/pom.xml @@ -46,8 +46,8 @@ independent-projects/ide-config independent-projects/arc independent-projects/bootstrap - independent-projects/tools independent-projects/qute + independent-projects/tools bom/runtime