From f33f3f6a4bd44c5acad47c258d6c9098cee11c53 Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 3 Nov 2017 19:22:33 -0700 Subject: [PATCH] Add a Spring Boot with Spring Session Data Geode sample showing an application using 'session' and 'request' scoped proxy bean. Resolves Issue #3. --- ...le-boot-gemfire-with-scoped-proxies.gradle | 93 ++++++++++++ .../main/java/sample/client/Application.java | 141 ++++++++++++++++++ .../client/model/RequestScopedProxyBean.java | 52 +++++++ .../client/model/SessionScopedProxyBean.java | 53 +++++++ .../java/sample/server/GemFireServer.java | 70 +++++++++ .../src/main/resources/templates/index.html | 40 +++++ 6 files changed, 449 insertions(+) create mode 100644 samples/boot/gemfire-with-scoped-proxies/spring-session-sample-boot-gemfire-with-scoped-proxies.gradle create mode 100644 samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/Application.java create mode 100644 samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/RequestScopedProxyBean.java create mode 100644 samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/SessionScopedProxyBean.java create mode 100644 samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/server/GemFireServer.java create mode 100644 samples/boot/gemfire-with-scoped-proxies/src/main/resources/templates/index.html diff --git a/samples/boot/gemfire-with-scoped-proxies/spring-session-sample-boot-gemfire-with-scoped-proxies.gradle b/samples/boot/gemfire-with-scoped-proxies/spring-session-sample-boot-gemfire-with-scoped-proxies.gradle new file mode 100644 index 0000000..34e690b --- /dev/null +++ b/samples/boot/gemfire-with-scoped-proxies/spring-session-sample-boot-gemfire-with-scoped-proxies.gradle @@ -0,0 +1,93 @@ +apply plugin: 'io.spring.convention.spring-sample-boot' +apply plugin: "application" + +apply from: IDE_GRADLE + +repositories { + mavenCentral() +} + +dependencies { + + compile project(':spring-session-data-geode') + compile "org.springframework.boot:spring-boot-starter-thymeleaf" + compile "org.springframework.boot:spring-boot-starter-web" + compile "org.webjars:bootstrap" + compile "org.webjars:webjars-locator" + + runtime "org.springframework.shell:spring-shell" + + testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile seleniumDependencies + +// integrationTestCompile seleniumDependencies + +// integrationTestRuntime "org.springframework.shell:spring-shell" + +} + +run { + doFirst { + mainClassName = 'sample.server.GemFireServer' + } +} + +bootJar { + mainClassName = 'sample.client.Application' +} + +task runGemFireServer() { + doLast { + ext.port = reservePort() + + println "Starting Apache Geode Server on port [$port] ..." + + def out = new StringBuilder() + def err = new StringBuilder() + + String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator) + + String[] commandLine = [ + 'java', '-server', '-ea', '-classpath', classpath, + //"-Dgemfire.log-file=gemfire-server.log", + //"-Dgemfire.log-level=config", + "-Dspring.session.data.geode.cache.server.port=$port", + 'sample.server.GemFireServer' + ] + + //println commandLine + + ext.process = commandLine.execute() + //ext.process = new ProcessBuilder().command(commandLine).redirectErrorStream(true).start(); + + ext.process.consumeProcessOutput(out, err) + + //println 'OUT: ' + out + //println 'ERR: ' + err + } +} + + +integrationTest { + dependsOn runGemFireServer + doFirst { + def port = reservePort() + systemProperties['management.port'] = 0 + systemProperties['server.port'] = port + //systemProperties['gemfire.log-file'] = "gemfire-client.log" + //systemProperties['gemfire.log-level'] = "config" + systemProperties['spring.session.data.geode.cache.server.port'] = runGemFireServer.port + } + doLast { + println 'Stopping Apache Geode Server...' + runGemFireServer.process?.destroyForcibly() + } +} + + +def reservePort() { + def socket = new ServerSocket(0) + def result = socket.localPort + socket.close() + result +} diff --git a/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/Application.java b/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/Application.java new file mode 100644 index 0000000..e47056b --- /dev/null +++ b/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/Application.java @@ -0,0 +1,141 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.client; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Collections; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer; +import org.springframework.data.gemfire.support.ConnectionEndpoint; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import sample.client.model.RequestScopedProxyBean; +import sample.client.model.SessionScopedProxyBean; + +/** + * A Spring Boot, GemFire cache client, web application that reveals the current state of the HTTP Session. + * + * @author John Blum + * @see javax.servlet.http.HttpSession + * @see org.springframework.boot.SpringApplication + * @see org.springframework.boot.autoconfigure.SpringBootApplication + * @see org.springframework.context.annotation.Bean + * @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession + * @see org.springframework.stereotype.Controller + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.client.Pool + * @since 1.2.1 + */ +@SuppressWarnings("unused") +// tag::class[] +@SpringBootApplication // <1> +@Controller // <2> +public class Application { + + static final String INDEX_TEMPLATE_VIEW_NAME = "index"; + static final String PING_RESPONSE = "PONG"; + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @ClientCacheApplication(name = "SpringSessionDataGeodeClientWithScopedProxiesBootSample", logLevel = "config", + pingInterval = 5000L, readTimeout = 15000, retryAttempts = 1, subscriptionEnabled = true) // <3> + @EnableGemFireHttpSession(poolName = "DEFAULT") // <4> + static class ClientCacheConfiguration { + + @Bean + static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean + ClientCacheConfigurer clientCacheServerPortConfigurer( + @Value("${spring.session.data.geode.cache.server.port:40404}") int port) { // <5> + + return (beanName, clientCacheFactoryBean) -> + clientCacheFactoryBean.setServers(Collections.singletonList( + new ConnectionEndpoint("localhost", port))); + } + } + + @Configuration + static class SpringWebMvcConfiguration { // <6> + + @Bean + public WebMvcConfigurer webMvcConfig() { + + return new WebMvcConfigurer() { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName(INDEX_TEMPLATE_VIEW_NAME); + } + }; + } + } + + @Autowired + private RequestScopedProxyBean requestBean; + + @Autowired + private SessionScopedProxyBean sessionBean; + + @ExceptionHandler + @ResponseBody + public String errorHandler(Throwable error) { + StringWriter writer = new StringWriter(); + error.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + + @RequestMapping(method = RequestMethod.GET, path = "/ping") + @ResponseBody + public String ping() { + return PING_RESPONSE; + } + + @RequestMapping(method = RequestMethod.GET, path = "/counts") + public String requestAndSessionInstanceCount(HttpServletRequest request, HttpSession session, Model model) { + + model.addAttribute("sessionId", session.getId()); + model.addAttribute("requestCount", this.requestBean.getCount()); + model.addAttribute("sessionCount", this.sessionBean.getCount()); + + return INDEX_TEMPLATE_VIEW_NAME; + } +} diff --git a/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/RequestScopedProxyBean.java b/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/RequestScopedProxyBean.java new file mode 100644 index 0000000..4dd98dd --- /dev/null +++ b/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/RequestScopedProxyBean.java @@ -0,0 +1,52 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.client.model; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + * The RequestScopedProxyBean class... + * + * @author John Blum + * @since 1.0.0 + */ +@Component +@RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) +@SuppressWarnings("unused") +public class RequestScopedProxyBean { + + private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0); + + private final int count; + + public RequestScopedProxyBean() { + this.count = INSTANCE_COUNTER.incrementAndGet(); + } + + public int getCount() { + return count; + } + + @Override + public String toString() { + return String.format("{ @type = '%s', count = %d }", getClass().getName(), getCount()); + } +} diff --git a/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/SessionScopedProxyBean.java b/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/SessionScopedProxyBean.java new file mode 100644 index 0000000..c79b599 --- /dev/null +++ b/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/SessionScopedProxyBean.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.client.model; + +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.SessionScope; + +/** + * The SessionScopedProxyBean class... + * + * @author John Blum + * @since 1.0.0 + */ +@Component +@SessionScope(proxyMode = ScopedProxyMode.TARGET_CLASS) +@SuppressWarnings("unused") +public class SessionScopedProxyBean implements Serializable { + + private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0); + + private final int count; + + public SessionScopedProxyBean() { + this.count = INSTANCE_COUNTER.incrementAndGet(); + } + + public int getCount() { + return count; + } + + @Override + public String toString() { + return String.format("{ @type = '%s', count = %d }", getClass().getName(), getCount()); + } +} diff --git a/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/server/GemFireServer.java b/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/server/GemFireServer.java new file mode 100644 index 0000000..6eb6fd7 --- /dev/null +++ b/samples/boot/gemfire-with-scoped-proxies/src/main/java/sample/server/GemFireServer.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.server; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.data.gemfire.config.annotation.CacheServerApplication; +import org.springframework.data.gemfire.config.annotation.CacheServerConfigurer; +import org.springframework.data.gemfire.config.annotation.EnableManager; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; + +/** + * A Spring Boot application bootstrapping a GemFire Cache Server JVM process. + * + * @author John Blum + * @see org.springframework.boot.SpringApplication + * @see org.springframework.boot.autoconfigure.SpringBootApplication + * @see org.springframework.context.annotation.Bean + * @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession + * @see org.apache.geode.cache.Cache + * @since 1.2.1 + */ +@SuppressWarnings("unused") +// tag::class[] +@SpringBootApplication +@CacheServerApplication(name = "SpringSessionDataGeodeServerWithScopedProxiesBootSample", logLevel = "config") +@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 10) +@EnableManager(start = true) +public class GemFireServer { + + public static void main(String[] args) { + + new SpringApplicationBuilder(GemFireServer.class) + .web(WebApplicationType.NONE) + .build() + .run(args); + } + + @Bean + static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean + CacheServerConfigurer cacheServerPortConfigurer( + @Value("${spring.session.data.geode.cache.server.port:40404}") int port) { + + return (beanName, cacheServerFactoryBean) -> + cacheServerFactoryBean.setPort(port); + } +} +// end::class[] diff --git a/samples/boot/gemfire-with-scoped-proxies/src/main/resources/templates/index.html b/samples/boot/gemfire-with-scoped-proxies/src/main/resources/templates/index.html new file mode 100644 index 0000000..00eb74c --- /dev/null +++ b/samples/boot/gemfire-with-scoped-proxies/src/main/resources/templates/index.html @@ -0,0 +1,40 @@ + + + + Request and Session Counts + + + + +
+

Description

+

+ This application demonstrates how both Spring 'request' and 'session' scoped (proxy) beans are utilized + in the context of Spring Session when GemFire is used to back the HTTP Session. +

+ + + + + + + + + + + + + + + + + + +
* Session IDSession CountRequest Count
count?00
+
+ +