From b013ee9887519282c7431e20e70ebbad56c524cd Mon Sep 17 00:00:00 2001 From: Thomas Mohme <2261413+tmohme@users.noreply.github.com> Date: Tue, 5 Feb 2019 20:35:06 +0100 Subject: [PATCH 1/2] Fix issue #104 - Bug: HotReload Exception with Composite --- .../helloGrettyMultiproject/README.md | 36 ++++++++++++ .../helloGrettyMultiproject/build.gradle | 31 +++++++++++ .../hellogrettyMultiproject/PageSpec.groovy | 36 ++++++++++++ .../ExampleServlet.java | 55 +++++++++++++++++++ .../hellogretty/templates/servletpage.html | 25 +++++++++ .../src/main/webapp/WEB-INF/filter.groovy | 3 + .../src/main/webapp/WEB-INF/web.xml | 33 +++++++++++ .../src/main/webapp/css/default.css | 14 +++++ .../src/main/webapp/index.html | 24 ++++++++ .../subproject/build.gradle | 1 + .../subproject/SomeClass.java | 5 ++ integrationTests/settings.gradle | 2 + .../org/akhikhl/gretty/ProjectUtils.groovy | 8 +++ .../gretty/scanner/BaseScannerManager.groovy | 7 ++- 14 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 integrationTests/helloGrettyMultiproject/README.md create mode 100644 integrationTests/helloGrettyMultiproject/build.gradle create mode 100644 integrationTests/helloGrettyMultiproject/src/integrationTest/groovy/org/akhikhl/examples/gretty/hellogrettyMultiproject/PageSpec.groovy create mode 100644 integrationTests/helloGrettyMultiproject/src/main/java/org/akhikhl/examples/gretty/hellogrettyMultiproject/ExampleServlet.java create mode 100644 integrationTests/helloGrettyMultiproject/src/main/resources/org/akhikhl/examples/gretty/hellogretty/templates/servletpage.html create mode 100644 integrationTests/helloGrettyMultiproject/src/main/webapp/WEB-INF/filter.groovy create mode 100644 integrationTests/helloGrettyMultiproject/src/main/webapp/WEB-INF/web.xml create mode 100644 integrationTests/helloGrettyMultiproject/src/main/webapp/css/default.css create mode 100644 integrationTests/helloGrettyMultiproject/src/main/webapp/index.html create mode 100644 integrationTests/helloGrettyMultiproject/subproject/build.gradle create mode 100644 integrationTests/helloGrettyMultiproject/subproject/src/main/java/org/akhikhl/examples/gretty/helloGrettyMultiproject/subproject/SomeClass.java diff --git a/integrationTests/helloGrettyMultiproject/README.md b/integrationTests/helloGrettyMultiproject/README.md new file mode 100644 index 000000000..09264f90b --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/README.md @@ -0,0 +1,36 @@ +# helloGrettyMultiproject + +Multiproject gretty example. + +## How to run + +```bash +cd examples/helloGrettyMultiproject +gradle appRun +``` + +## How to test + +```bash +cd examples/helloGrettyMultiproject +gradle integrationTest +``` + +Unfortunately there's no automated test for the reload mechanism, because the `AppBeforeIntegrationTestTask` task +delibrately disables scanning on integration tests. +The existing automated test verifies only that gretty is usable for multiproject builds. + +Verifying the hot-reload behavior has to be done manually: +1. start the server with `gradle appRun` in one terminal +2. use another terminal to `touch subproject/src/main/java/org/akhikhl/examples/gretty/helloGrettyMultiproject/subproject/SomeClass.java` +3. verify in the first terminal that gretty recompiled the project and reloaded the app +4. verify the page is still usable in a browser + +## How to build a product + + +```bash +cd examples/helloGrettyMultiproject +gradle buildProduct +``` + diff --git a/integrationTests/helloGrettyMultiproject/build.gradle b/integrationTests/helloGrettyMultiproject/build.gradle new file mode 100644 index 000000000..5fa4bd734 --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'war' +apply plugin: 'org.gretty' +apply plugin: 'org.gretty.internal.integrationTests.IntegrationTestPlugin' + +dependencies { + implementation(project(":helloGrettyMultiproject:subproject")) + compile 'org.webjars:bootstrap:3.2.0' + compile 'org.webjars:jquery:2.1.1' + // We use Velocity for example of template processing within the webapp. + compile 'org.apache.velocity:velocity:1.7' +} + +gretty { + scanDependencies = true // undocumented + + recompileOnSourceChange = true + reloadOnClassChange = true + reloadOnConfigChange = true + reloadOnLibChange = true +} + +test { + testLogging.showStandardStreams = true +} + +war { + archiveName 'helloGrettyMultiproject.war' +} + +defineIntegrationTest() +testAll.dependsOn defineIntegrationTestAllContainers(), 'buildProduct' diff --git a/integrationTests/helloGrettyMultiproject/src/integrationTest/groovy/org/akhikhl/examples/gretty/hellogrettyMultiproject/PageSpec.groovy b/integrationTests/helloGrettyMultiproject/src/integrationTest/groovy/org/akhikhl/examples/gretty/hellogrettyMultiproject/PageSpec.groovy new file mode 100644 index 000000000..9c849cdaa --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/src/integrationTest/groovy/org/akhikhl/examples/gretty/hellogrettyMultiproject/PageSpec.groovy @@ -0,0 +1,36 @@ +/* + * Gretty + * + * Copyright (C) 2013-2015 Andrey Hihlovskiy and contributors. + * + * See the file "LICENSE" for copying and usage permission. + * See the file "CONTRIBUTORS" for complete list of contributors. + */ +package org.akhikhl.examples.gretty.hellogrettyMultiproject + +import geb.spock.GebReportingSpec + +class PageSpec extends GebReportingSpec { + + private static String baseURI + + void setupSpec() { + baseURI = System.getProperty('gretty.baseURI') + } + + def 'should get expected static page'() { + when: + go "${baseURI}/index.html" + then: + $('h1').text() == 'Hello, world!' + $('p', 0).text() == /This is static HTML page./ + } + + def 'should get expected response from servlet'() { + when: + go "${baseURI}/dynamic" + then: + $('h1').text() == 'Hello, world!' + $('p', 0).text() == /This is dynamic HTML page generated by servlet./ + } +} diff --git a/integrationTests/helloGrettyMultiproject/src/main/java/org/akhikhl/examples/gretty/hellogrettyMultiproject/ExampleServlet.java b/integrationTests/helloGrettyMultiproject/src/main/java/org/akhikhl/examples/gretty/hellogrettyMultiproject/ExampleServlet.java new file mode 100644 index 000000000..522c26312 --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/src/main/java/org/akhikhl/examples/gretty/hellogrettyMultiproject/ExampleServlet.java @@ -0,0 +1,55 @@ +/* + * Gretty + * + * Copyright (C) 2013-2015 Andrey Hihlovskiy and contributors. + * + * See the file "LICENSE" for copying and usage permission. + * See the file "CONTRIBUTORS" for complete list of contributors. + */ +package org.akhikhl.examples.gretty.hellogrettyMultiproject; + +import org.akhikhl.examples.gretty.hellogrettyMultiproject.subproject.SomeClass; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + + +public class ExampleServlet extends HttpServlet { + + private static final long serialVersionUID = -6506276378398106663L; + + private VelocityEngine ve = new VelocityEngine(); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + Template template = ve.getTemplate("/org/akhikhl/examples/gretty/hellogretty/templates/servletpage.html", "UTF-8"); + VelocityContext context = new VelocityContext(); + context.put("contextPath", request.getContextPath()); + context.put("today", new java.util.Date()); + PrintWriter out = response.getWriter(); + SomeClass.doNothing(); + try { + template.merge(context, out); + out.flush(); + } finally { + out.close(); + } + } + + @Override + public void init() throws ServletException { + super.init(); + ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); + ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); + ve.init(); + } +} diff --git a/integrationTests/helloGrettyMultiproject/src/main/resources/org/akhikhl/examples/gretty/hellogretty/templates/servletpage.html b/integrationTests/helloGrettyMultiproject/src/main/resources/org/akhikhl/examples/gretty/hellogretty/templates/servletpage.html new file mode 100644 index 000000000..2fee74a64 --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/src/main/resources/org/akhikhl/examples/gretty/hellogretty/templates/servletpage.html @@ -0,0 +1,25 @@ + + + + Hello-world page + + + + + + + + + +
+
+
+

Hello, world!

+

This is dynamic HTML page generated by servlet.

+

Today is $today.

+

Click here to see static page.

+
+
+
+ + diff --git a/integrationTests/helloGrettyMultiproject/src/main/webapp/WEB-INF/filter.groovy b/integrationTests/helloGrettyMultiproject/src/main/webapp/WEB-INF/filter.groovy new file mode 100644 index 000000000..812d3d5d4 --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/src/main/webapp/WEB-INF/filter.groovy @@ -0,0 +1,3 @@ +filter relPath: '/', { + redirect 'index.html' +} diff --git a/integrationTests/helloGrettyMultiproject/src/main/webapp/WEB-INF/web.xml b/integrationTests/helloGrettyMultiproject/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..e458ef9da --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,33 @@ + + + + RedirectFilter + org.akhikhl.gretty.RedirectFilter + + + RedirectFilter + /* + REQUEST + FORWARD + + + + jsp + *.html + + + + ExampleServlet + ExampleServlet + org.akhikhl.examples.gretty.hellogrettyMultiproject.ExampleServlet + 1 + + + + ExampleServlet + /dynamic + + diff --git a/integrationTests/helloGrettyMultiproject/src/main/webapp/css/default.css b/integrationTests/helloGrettyMultiproject/src/main/webapp/css/default.css new file mode 100644 index 000000000..09e4763bb --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/src/main/webapp/css/default.css @@ -0,0 +1,14 @@ +body { + padding-top: 20px; + font-family: 'Open Sans', sans-serif; + font-size: 18px; +} + +h1 { + font-weight: 400; + font-size: 40px; +} + +.margin-base-vertical { + margin: 40px 0; +} diff --git a/integrationTests/helloGrettyMultiproject/src/main/webapp/index.html b/integrationTests/helloGrettyMultiproject/src/main/webapp/index.html new file mode 100644 index 000000000..87e2f4029 --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/src/main/webapp/index.html @@ -0,0 +1,24 @@ + + + + Hello-world page + + + + + + + + + +
+
+
+

Hello, world!

+

This is static HTML page.

+

Click here to see dynamic page generated by servlet.

+
+
+
+ + diff --git a/integrationTests/helloGrettyMultiproject/subproject/build.gradle b/integrationTests/helloGrettyMultiproject/subproject/build.gradle new file mode 100644 index 000000000..bbfeb03c2 --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/subproject/build.gradle @@ -0,0 +1 @@ +apply plugin: 'java' diff --git a/integrationTests/helloGrettyMultiproject/subproject/src/main/java/org/akhikhl/examples/gretty/helloGrettyMultiproject/subproject/SomeClass.java b/integrationTests/helloGrettyMultiproject/subproject/src/main/java/org/akhikhl/examples/gretty/helloGrettyMultiproject/subproject/SomeClass.java new file mode 100644 index 000000000..b33d2cdf7 --- /dev/null +++ b/integrationTests/helloGrettyMultiproject/subproject/src/main/java/org/akhikhl/examples/gretty/helloGrettyMultiproject/subproject/SomeClass.java @@ -0,0 +1,5 @@ +package org.akhikhl.examples.gretty.hellogrettyMultiproject.subproject; + +public class SomeClass { + public static void doNothing() {} +} \ No newline at end of file diff --git a/integrationTests/settings.gradle b/integrationTests/settings.gradle index d0a681e83..395eb953e 100644 --- a/integrationTests/settings.gradle +++ b/integrationTests/settings.gradle @@ -1,5 +1,7 @@ rootProject.name = rootProjectName include 'helloGretty' +include 'helloGrettyMultiproject' +include 'helloGrettyMultiproject:subproject' include 'helloGrettySecure' include 'helloGrettyOverlay' include 'helloJersey' diff --git a/libs/gretty/src/main/groovy/org/akhikhl/gretty/ProjectUtils.groovy b/libs/gretty/src/main/groovy/org/akhikhl/gretty/ProjectUtils.groovy index ec96f724e..479b3c168 100644 --- a/libs/gretty/src/main/groovy/org/akhikhl/gretty/ProjectUtils.groovy +++ b/libs/gretty/src/main/groovy/org/akhikhl/gretty/ProjectUtils.groovy @@ -440,6 +440,14 @@ final class ProjectUtils { wconfig.contextConfigFile = null } + static List getDependencyProjects(Project project, List configurationNames) { + def result = [] as Set + configurationNames.each { + result += getDependencyProjects(project, it) + } + return result as List + } + static List getDependencyProjects(Project project, String configurationName) { project.configurations[configurationName].dependencies.withType(ProjectDependency).collect{ it.dependencyProject } } diff --git a/libs/gretty/src/main/groovy/org/akhikhl/gretty/scanner/BaseScannerManager.groovy b/libs/gretty/src/main/groovy/org/akhikhl/gretty/scanner/BaseScannerManager.groovy index c3de4575c..c0f0ebb4b 100644 --- a/libs/gretty/src/main/groovy/org/akhikhl/gretty/scanner/BaseScannerManager.groovy +++ b/libs/gretty/src/main/groovy/org/akhikhl/gretty/scanner/BaseScannerManager.groovy @@ -84,7 +84,10 @@ abstract class BaseScannerManager implements ScannerManager { } protected static void collectDependenciesSourceSets(Collection scanDirs, Project p) { - List dependencyProjects = ProjectUtils.getDependencyProjects(p, 'implementation') + // collect project dependencies of _all_ configurations that the build author _might_ have used + // and that are relevant at runtime + List dependencyProjects = + ProjectUtils.getDependencyProjects(p, ['compile', 'implementation', 'runtime', 'runtimeOnly']) for(Project project: dependencyProjects) { // adding sourceSets of dependecy project scanDirs.addAll(project.sourceSets.main.allSource.srcDirs) @@ -162,7 +165,7 @@ abstract class BaseScannerManager implements ScannerManager { for(String f in changedFiles) { if(f.endsWith('.jar')) { List dependantWebAppProjects = webapps.findAll { - it.projectPath && project.project(it.projectPath).configurations.implementation.resolvedConfiguration.resolvedArtifacts.find { + it.projectPath && project.project(it.projectPath).configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.find { it.file.absolutePath == f } } if(dependantWebAppProjects) { From 1a0dedc397cfe87b256939236aaa8edeb7b81b1b Mon Sep 17 00:00:00 2001 From: Boris Petrov Date: Mon, 14 Sep 2020 10:08:48 +0300 Subject: [PATCH 2/2] Correctly populate the `writer` field in `ServerStartEventImpl` See https://github.com/gretty-gradle-plugin/gretty/pull/113#issuecomment-692909723 --- .../src/main/groovy/org/akhikhl/gretty/Runner.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gretty-runner/src/main/groovy/org/akhikhl/gretty/Runner.groovy b/libs/gretty-runner/src/main/groovy/org/akhikhl/gretty/Runner.groovy index 67c31d1bf..81c39fea0 100644 --- a/libs/gretty-runner/src/main/groovy/org/akhikhl/gretty/Runner.groovy +++ b/libs/gretty-runner/src/main/groovy/org/akhikhl/gretty/Runner.groovy @@ -144,7 +144,7 @@ final class Runner { } else if(data == 'restartWithEvent') { serverManager.stopServer() - serverManager.startServer(new ServerStartEventImpl()) + serverManager.startServer(new ServerStartEventImpl(writer)) } else if (data.startsWith('redeploy ')) { List webappList = data.replace('redeploy ', '').split(' ').toList()