diff --git a/spring-geode-samples/caching/inline/spring-geode-samples-caching-inline.gradle b/spring-geode-samples/caching/inline/spring-geode-samples-caching-inline.gradle new file mode 100644 index 00000000..ad81e11b --- /dev/null +++ b/spring-geode-samples/caching/inline/spring-geode-samples-caching-inline.gradle @@ -0,0 +1,37 @@ +apply plugin: 'io.spring.convention.spring-sample-boot' + +description = "Spring Geode Sample demonstrating Spring's Cache Abstraction using Apache Geode as the caching provider with Inline Caching." + +dependencies { + + compile project(":spring-geode-starter") + + compile ("org.springframework.boot:spring-boot-starter-data-jpa") { + exclude group: "org.apache.logging.log4j", module: "log4j-to-slf4j" + } + + compile ("org.springframework.boot:spring-boot-starter-web") { + exclude group: "org.apache.logging.log4j", module: "log4j-to-slf4j" + } + + compile "org.projectlombok:lombok" + + runtime "javax.cache:cache-api" + runtime "org.hsqldb:hsqldb" + runtime "org.springframework.shell:spring-shell" + + runtime ("org.springframework.boot:spring-boot-starter-jetty") { + exclude group: "org.apache.logging.log4j", module: "log4j-to-slf4j" + } + + testCompile("org.springframework.boot:spring-boot-starter-test") { + exclude group: "org.apache.logging.log4j", module: "log4j-to-slf4j" + } + + testCompile "org.springframework.data:spring-data-geode-test" + +} + +bootJar { + mainClassName = 'example.app.caching.inline.BootGeodeInlineCachingApplication' +} diff --git a/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/BootGeodeInlineCachingApplication.java b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/BootGeodeInlineCachingApplication.java new file mode 100644 index 00000000..b7514235 --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/BootGeodeInlineCachingApplication.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package example.app.caching.inline; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Spring Boot Web application to demonstrate {@literal Inline Caching}. + * + * @author John Blum + * @see org.springframework.boot.SpringApplication + * @see org.springframework.boot.autoconfigure.SpringBootApplication + * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication + * @see org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions + * @since 1.1.0 + */ +@SpringBootApplication +public class BootGeodeInlineCachingApplication { + + public static void main(String[] args) { + SpringApplication.run(BootGeodeInlineCachingApplication.class, args); + } +} diff --git a/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/config/CalculatorConfiguration.java b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/config/CalculatorConfiguration.java new file mode 100644 index 00000000..ce7c2cec --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/config/CalculatorConfiguration.java @@ -0,0 +1,81 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package example.app.caching.inline.config; + +import java.util.Arrays; + +import org.apache.geode.cache.client.ClientRegionShortcut; + +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions; +import org.springframework.geode.cache.InlineCachingRegionConfigurer; + +import example.app.caching.inline.model.Operator; +import example.app.caching.inline.model.ResultHolder; +import example.app.caching.inline.repo.CalculatorRepository; + +/** + * Spring {@link Configuration} class used to configure Apache Geode as a caching provider as well as configure + * the target of JPA entity scan for application persistent entities. + * + * Additionally, a custom {@link KeyGenerator} bean definition is declared and used in caching to sync the keys + * used by both the cache and the database. + * + * @author John Blum + * @see org.springframework.boot.autoconfigure.domain.EntityScan + * @see org.springframework.cache.interceptor.KeyGenerator + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions + * @see org.springframework.geode.cache.InlineCachingRegionConfigurer + * @see example.app.caching.inline.model.ResultHolder + * @see example.app.caching.inline.repo.CalculatorRepository + * @since 1.1.0 + */ +// tag::class[] +@Configuration +@EnableCachingDefinedRegions(clientRegionShortcut = ClientRegionShortcut.LOCAL) +@EntityScan(basePackageClasses = ResultHolder.class) +@SuppressWarnings("unused") +public class CalculatorConfiguration { + + @Bean + InlineCachingRegionConfigurer inlineCachingForCalculatorApplicationRegionsConfigurer( + CalculatorRepository calculatorRepository) { + + return new InlineCachingRegionConfigurer<>(calculatorRepository, + regionBeanName -> Arrays.asList("Factorials", "SquareRoots").contains(regionBeanName)); + } + + // tag::key-generator[] + @Bean + KeyGenerator resultKeyGenerator() { + + return (target, method, arguments) -> { + + int operand = Integer.valueOf(String.valueOf(arguments[0])); + + Operator operator = "sqrt".equals(method.getName()) ? Operator.SQUARE_ROOT : Operator.FACTORIAL; + + return ResultHolder.ResultKey.of(operand, operator); + }; + } + // end::key-generator[] +} +// end::class[] diff --git a/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/model/Operator.java b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/model/Operator.java new file mode 100644 index 00000000..2239b427 --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/model/Operator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package example.app.caching.inline.model; + +/** + * An {@link Class enumerated type} enumerating various mathematical operators or functions. + * + * @author John Blum + * @since 1.1.0 + */ +// tag::class[] +public enum Operator { + + FACTORIAL("factorial(%1$d) = %2$d"), + SQUARE_ROOT("sqrt(%1$d) = %2$d"); + + private final String representation; + + Operator(String definition) { + this.representation = definition; + } + + @Override + public String toString() { + return this.representation; + } + + public String toString(Object... args) { + return String.format(toString(), args); + } +} +// end::class[] diff --git a/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/model/ResultHolder.java b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/model/ResultHolder.java new file mode 100644 index 00000000..de806906 --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/model/ResultHolder.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package example.app.caching.inline.model; + +import java.io.Serializable; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Abstract Data Type (ADT) and persistent entity modeling the results of a mathematical calculation. + * + * @author John Blum + * @see java.io.Serializable + * @see javax.persistence.Entity + * @see javax.persistence.IdClass + * @see javax.persistence.Table + * @since 1.1.0 + */ +// tag::class[] +@Entity +@Getter +@IdClass(ResultHolder.ResultKey.class) +@EqualsAndHashCode(of = { "operand", "operator" }) +@RequiredArgsConstructor(staticName = "of") +@Table(name = "Calculations") +public class ResultHolder implements Serializable { + + @Id @NonNull + private Integer operand; + + @Id + @NonNull + @Enumerated(EnumType.STRING) + private Operator operator; + + @NonNull + private Integer result; + + protected ResultHolder() { } + + @Override + public String toString() { + return getOperator().toString(getOperand(), getResult()); + } + + @Getter + @EqualsAndHashCode + @RequiredArgsConstructor(staticName = "of") + public static class ResultKey implements Serializable { + + @NonNull + private Integer operand; + + @NonNull + private Operator operator; + + protected ResultKey() { } + + } +} +// end::class[] diff --git a/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/repo/CalculatorRepository.java b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/repo/CalculatorRepository.java new file mode 100644 index 00000000..4cc24e9a --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/repo/CalculatorRepository.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package example.app.caching.inline.repo; + +import java.util.Optional; + +import org.springframework.data.repository.CrudRepository; + +import example.app.caching.inline.model.Operator; +import example.app.caching.inline.model.ResultHolder; + +/** + * Spring Data {@link CrudRepository}, a.k.a. Data Access Object (DAO) used to perform basic CRUD and simple query + * data access operations on {@link ResultHolder} objects to/from the backend data store. + * + * @author John Blum + * @see org.springframework.data.repository.CrudRepository + * @see example.app.caching.inline.model.ResultHolder + * @since 1.1.0 + */ +@SuppressWarnings("unused") +// tag::class[] +public interface CalculatorRepository extends CrudRepository { + + Optional findByOperandEqualsAndOperatorEquals(Number operand, Operator operator); + +} +// end::class[] diff --git a/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/service/CalculatorService.java b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/service/CalculatorService.java new file mode 100644 index 00000000..75542f2d --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/service/CalculatorService.java @@ -0,0 +1,81 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package example.app.caching.inline.service; + +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import example.app.caching.inline.model.Operator; +import example.app.caching.inline.model.ResultHolder; +import example.app.caching.inline.service.support.AbstractCacheableService; + +/** + * Spring {@link Service} class implementing mathematical operators, similar to calculator functions, + * such as {@literal factorial} and so on. + * + * In addition, given that most mathematical functions yield the same result when given the same input, this service + * also employs caching using Spring's Cache Abstraction and Apache Geode as the caching provider. This class provides + * additional functionality to ascertain whether a cache hit/miss occurred. + * + * @author John Blum + * @see org.springframework.cache.annotation.Cacheable + * @see org.springframework.stereotype.Service + * @see example.app.caching.inline.model.Operator + * @see example.app.caching.inline.model.ResultHolder + * @see example.app.caching.inline.service.support.AbstractCacheableService + * @since 1.1.0 + */ +// tag::class[] +@Service +public class CalculatorService extends AbstractCacheableService { + + @Cacheable(value = "Factorials", keyGenerator = "resultKeyGenerator") + public ResultHolder factorial(int number) { + + this.cacheMiss.set(true); + + Assert.isTrue(number >= 0L, + String.format("Number [%d] must be greater than equal to 0", number)); + + simulateLatency(); + + if (number <= 2) { + return ResultHolder.of(number, Operator.FACTORIAL, number == 2 ? 2 : 1); + } + + int result = number; + + while (--number > 1) { + result *= number; + } + + return ResultHolder.of(number, Operator.FACTORIAL, result); + } + + @Cacheable(value = "SquareRoots", keyGenerator = "resultKeyGenerator") + public ResultHolder sqrt(int number) { + + this.cacheMiss.set(true); + + Assert.isTrue(number >= 0, String.format("Number [%d] must be greater than equal to 0", number)); + + simulateLatency(); + + return ResultHolder.of(number, Operator.SQUARE_ROOT, Double.valueOf(Math.sqrt(number)).intValue()); + } +} +// end::class[] diff --git a/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/service/support/AbstractCacheableService.java b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/service/support/AbstractCacheableService.java new file mode 100644 index 00000000..eebf2797 --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/java/example/app/caching/inline/service/support/AbstractCacheableService.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package example.app.caching.inline.service.support; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.springframework.stereotype.Service; + +/** + * Abstract based class for implementing cacheable {@link Service services} along with additional functionality to + * ascertain whether a cacheable operation led to a cache hit or a cache miss. + * + * @author John Blum + * @since 1.1.0 + */ +public abstract class AbstractCacheableService { + + protected final AtomicBoolean cacheMiss = new AtomicBoolean(false); + + public boolean isCacheHit() { + return !isCacheMiss(); + } + + public boolean isCacheMiss() { + return this.cacheMiss.compareAndSet(true,false); + } + + protected long delayInMilliseconds() { + return 3000L; + } + + protected boolean simulateLatency() { + + try { + Thread.sleep(delayInMilliseconds()); + return true; + } + catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + return false; + } + } +} diff --git a/spring-geode-samples/caching/inline/src/main/resources/application.properties b/spring-geode-samples/caching/inline/src/main/resources/application.properties new file mode 100644 index 00000000..7e1fbeaf --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/resources/application.properties @@ -0,0 +1,5 @@ +# Spring Boot application.properties to configure the Inline Caching Example Application + +spring.data.gemfire.cache.log-level=${gemfire.log-level:error} +spring.jpa.hibernate.ddl-auto=none +spring.jpa.show-sql=false diff --git a/spring-geode-samples/caching/inline/src/main/resources/data.sql b/spring-geode-samples/caching/inline/src/main/resources/data.sql new file mode 100644 index 00000000..bb32a7ec --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/resources/data.sql @@ -0,0 +1,6 @@ +INSERT INTO calculations (operand, operator, result) VALUES (5, 'FACTORIAL', 120); +INSERT INTO calculations (operand, operator, result) VALUES (7, 'FACTORIAL', 5040); +INSERT INTO calculations (operand, operator, result) VALUES (9, 'FACTORIAL', 362880); +INSERT INTO calculations (operand, operator, result) VALUES (16, 'SQUARE_ROOT', 4); +INSERT INTO calculations (operand, operator, result) VALUES (64, 'SQUARE_ROOT', 8); +INSERT INTO calculations (operand, operator, result) VALUES (256, 'SQUARE_ROOT', 16); diff --git a/spring-geode-samples/caching/inline/src/main/resources/schema.sql b/spring-geode-samples/caching/inline/src/main/resources/schema.sql new file mode 100644 index 00000000..9e41f57d --- /dev/null +++ b/spring-geode-samples/caching/inline/src/main/resources/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS calculations ( + operand INTEGER NOT NULL, + operator VARCHAR(256) NOT NULL, + result INTEGER NOT NULL, + PRIMARY KEY (operand, operator) +);