Create base Spring Geode Sample Example application on Async Inline Caching.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
plugins {
|
||||
id "io.freefair.lombok" version "5.3.0"
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
description = "Spring Geode Sample demonstrating Spring's Cache Abstraction using Apache Geode as the caching provider for Asynchronous Inline Caching."
|
||||
|
||||
dependencies {
|
||||
|
||||
compile project(":spring-geode-starter")
|
||||
|
||||
compile "org.projectlombok:lombok"
|
||||
compile "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
|
||||
runtime "org.hsqldb:hsqldb"
|
||||
|
||||
testCompile project(":spring-geode-starter-test")
|
||||
|
||||
testCompile "org.awaitility:awaitility:$awaitilityVersion"
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
||||
|
||||
}
|
||||
|
||||
bootJar {
|
||||
mainClassName = 'example.app.caching.inline.BootGeodeAsyncInlineCachingClientApplication'
|
||||
}
|
||||
|
||||
bootRun {
|
||||
main = 'example.app.caching.inline.BootGeodeAsyncInlineClientCachingApplication'
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client;
|
||||
|
||||
import org.apache.geode.cache.RegionShortcut;
|
||||
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
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.annotation.Import;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
|
||||
import org.springframework.geode.config.annotation.UseMemberName;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import example.app.caching.inline.async.client.model.GolfTournament;
|
||||
import example.app.caching.inline.async.client.model.support.GolfCourseBuilder;
|
||||
import example.app.caching.inline.async.client.model.support.GolferBuilder;
|
||||
import example.app.caching.inline.async.client.service.GolfTournamentService;
|
||||
import example.app.caching.inline.async.config.AsyncInlineCachingConfiguration;
|
||||
|
||||
/**
|
||||
* {@link SpringBootApplication} class simulating a golf tournament management application.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.boot.ApplicationRunner
|
||||
* @see org.springframework.boot.SpringApplication
|
||||
* @see org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.context.annotation.Profile
|
||||
* @see org.springframework.data.gemfire.config.annotation.EnableCachingDefinedRegions
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication
|
||||
* @see org.springframework.geode.cache.AsyncInlineCachingRegionConfigurer
|
||||
* @see org.springframework.geode.config.annotation.UseMemberName
|
||||
* @see org.springframework.scheduling.annotation.EnableScheduling
|
||||
* @see example.app.caching.inline.async.client.model.GolfCourse
|
||||
* @see example.app.caching.inline.async.client.model.GolfTournament
|
||||
* @see example.app.caching.inline.async.client.model.Golfer
|
||||
* @see example.app.caching.inline.async.client.service.GolfTournamentService
|
||||
* @see example.app.caching.inline.async.config.AsyncInlineCachingConfiguration
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@SuppressWarnings("unused")
|
||||
public class BootGeodeAsyncInlineCachingClientApplication {
|
||||
|
||||
private static final String APPLICATION_NAME = "GolfClientApplication";
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BootGeodeAsyncInlineCachingClientApplication.class, args);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
static class GolfApplicationConfiguration {
|
||||
|
||||
@Bean
|
||||
ApplicationRunner runGolfTournament(GolfTournamentService golfTournamentService) {
|
||||
|
||||
return args -> {
|
||||
|
||||
GolfTournament golfTournament = GolfTournament.newGolfTournament("Masters")
|
||||
.at(GolfCourseBuilder.buildAugustaNational())
|
||||
.register(GolferBuilder.buildGolfers(GolferBuilder.FAVORITE_GOLFER_NAMES))
|
||||
.buildPairings()
|
||||
.play();
|
||||
|
||||
golfTournamentService.manage(golfTournament);
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@UseMemberName(APPLICATION_NAME)
|
||||
@EnableCachingDefinedRegions(serverRegionShortcut = RegionShortcut.LOCAL)
|
||||
static class GeodeConfiguration { }
|
||||
|
||||
@PeerCacheApplication
|
||||
@Profile("peer-cache")
|
||||
@Import(AsyncInlineCachingConfiguration.class)
|
||||
static class PeerCacheApplicationConfiguration { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.shiro.util.CollectionUtils;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Abstract Data Type (ADT) modeling a golf course.
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Getter
|
||||
@ToString(of = "name")
|
||||
@EqualsAndHashCode(of = "name")
|
||||
@RequiredArgsConstructor(staticName = "newGolfCourse")
|
||||
@SuppressWarnings("unused")
|
||||
public class GolfCourse {
|
||||
|
||||
public static final int STANDARD_PAR_FOR_COURSE = 72;
|
||||
|
||||
public static final Set<Integer> VALID_PARS_FOR_HOLE =
|
||||
Collections.unmodifiableSet(CollectionUtils.asSet(3, 4, 5));
|
||||
|
||||
@NonNull
|
||||
private final String name;
|
||||
|
||||
private final List<Integer> parForHole = new ArrayList<>(18);
|
||||
|
||||
public int getPar(int hole) {
|
||||
|
||||
assertValidHoleNumber(hole);
|
||||
|
||||
return this.parForHole.get(indexForHole(hole));
|
||||
}
|
||||
|
||||
public int getParForCourse() {
|
||||
|
||||
return this.parForHole.stream()
|
||||
.reduce(Integer::sum)
|
||||
.orElse(STANDARD_PAR_FOR_COURSE);
|
||||
}
|
||||
|
||||
public GolfCourse withHole(int holeNumber, int par) {
|
||||
|
||||
assertValidHoleNumber(holeNumber);
|
||||
assertValidParForHole(par, holeNumber);
|
||||
|
||||
this.parForHole.set(indexForHole(holeNumber), par);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void assertValidHoleNumber(int hole) {
|
||||
Assert.isTrue(isValidHoleNumber(hole),
|
||||
() -> String.format("Hole number [%d] must be 1 through 18", hole));
|
||||
}
|
||||
|
||||
private void assertValidParForHole(int par, int hole) {
|
||||
Assert.isTrue(isValidPar(par),
|
||||
() -> String.format("Par [%1$d] for hole [%2$d] must be in [%3$s]", par, hole, VALID_PARS_FOR_HOLE));
|
||||
}
|
||||
|
||||
private int indexForHole(int hole) {
|
||||
return hole - 1;
|
||||
}
|
||||
|
||||
public boolean isValidHoleNumber(int hole) {
|
||||
return hole >= 1 && hole <= 18;
|
||||
}
|
||||
|
||||
public boolean isValidPar(int par) {
|
||||
return VALID_PARS_FOR_HOLE.contains(par);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Abstract Data Type (ADT) modeling a golf tournament.
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Getter
|
||||
@ToString
|
||||
@RequiredArgsConstructor(staticName = "newGolfTournament")
|
||||
@SuppressWarnings("unused")
|
||||
public class GolfTournament implements Iterable<GolfTournament.Pairing> {
|
||||
|
||||
@NonNull
|
||||
private final String name;
|
||||
|
||||
private GolfCourse golfCourse;
|
||||
|
||||
private final List<Pairing> pairings = new ArrayList<>();
|
||||
|
||||
private final Set<Golfer> players = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Iterator<GolfTournament.Pairing> iterator() {
|
||||
return Collections.unmodifiableList(this.pairings).iterator();
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
|
||||
Set<Pairing> finishedPairings = new HashSet<>(this.pairings.size());
|
||||
|
||||
for (Pairing pairing : this) {
|
||||
if (pairing.getHole() < 18) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
finishedPairings.add(pairing);
|
||||
}
|
||||
}
|
||||
|
||||
this.pairings.removeAll(finishedPairings);
|
||||
|
||||
return this.pairings.isEmpty();
|
||||
}
|
||||
|
||||
public GolfTournament at(GolfCourse golfCourse) {
|
||||
|
||||
Assert.notNull(golfCourse, "Golf Course must not be null");
|
||||
|
||||
this.golfCourse = golfCourse;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public GolfTournament buildPairings() {
|
||||
|
||||
Assert.notEmpty(this.players,
|
||||
() -> String.format("No players are registered for this golf tournament [%s]", getName()));
|
||||
|
||||
Assert.isTrue(this.players.size() % 2 == 0,
|
||||
() -> String.format("An even number of players must register to play this golf tournament [%s]; currently at [%d]",
|
||||
getName(), this.players.size()));
|
||||
|
||||
List<Golfer> playersToPair = new ArrayList<>(this.players);
|
||||
|
||||
Collections.shuffle(playersToPair);
|
||||
|
||||
for (int index = 0, size = playersToPair.size(); index < size; index += 2) {
|
||||
this.pairings.add(Pairing.of(playersToPair.get(index), playersToPair.get(index + 1)));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public GolfTournament play() {
|
||||
|
||||
Assert.state(this.golfCourse != null, "No golf course was declared");
|
||||
Assert.state(!this.players.isEmpty(), "Golfers must register to play before the golf tournament is played");
|
||||
Assert.state(!this.pairings.isEmpty(), "Pairings must be formed before the golf tournament is played");
|
||||
Assert.state(!isFinished(), () -> String.format("Golf tournament [%s] has already been played", getName()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public GolfTournament register(Golfer... players) {
|
||||
return register(Arrays.asList(ArrayUtils.nullSafeArray(players, Golfer.class)));
|
||||
}
|
||||
|
||||
public GolfTournament register(Iterable<Golfer> players) {
|
||||
|
||||
StreamSupport.stream(CollectionUtils.nullSafeIterable(players).spliterator(), false)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(this.players::add);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public static class Pairing {
|
||||
|
||||
@NonNull
|
||||
private final Golfer playerOne;
|
||||
|
||||
@NonNull
|
||||
private final Golfer playerTwo;
|
||||
|
||||
public int getHole() {
|
||||
return getPlayerOne().getHole();
|
||||
}
|
||||
|
||||
public void setHole(int hole) {
|
||||
this.playerOne.setHole(hole);
|
||||
this.playerTwo.setHole(hole);
|
||||
}
|
||||
|
||||
public int playNextHole() {
|
||||
return getHole() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client.model;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Abstract Data Type (ADT) modeling a person who plays golf.
|
||||
*
|
||||
* In addition to the {@link Golfer Golfer's} {@link String name}, this class tracks the current {@link Integer hole}
|
||||
* and {@link Integer score} of the {@link Golfer} when s/he competes/plays in a golf tournament.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see javax.persistence.Entity
|
||||
* @see javax.persistence.Table
|
||||
* @see org.springframework.data.annotation.Id
|
||||
* @see org.springframework.data.gemfire.mapping.annotation.Region
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString(of = "name")
|
||||
@Table(name = "golfers")
|
||||
@Region(name = "Golfers")
|
||||
@EqualsAndHashCode(of = "name")
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@RequiredArgsConstructor(staticName = "newGolfer")
|
||||
@SuppressWarnings("unused")
|
||||
public class Golfer implements Comparable<Golfer> {
|
||||
|
||||
@javax.persistence.Id @Id @NonNull
|
||||
private String name;
|
||||
|
||||
@Setter
|
||||
private Integer hole = 0;
|
||||
|
||||
@Setter
|
||||
private Integer score = 0;
|
||||
|
||||
@Override
|
||||
public int compareTo(Golfer other) {
|
||||
return this.getName().compareTo(other.getName());
|
||||
}
|
||||
|
||||
private boolean isValidHole(int hole) {
|
||||
return hole >= 1 && hole <= 18;
|
||||
}
|
||||
|
||||
public Golfer on(int hole) {
|
||||
|
||||
Assert.isTrue(isValidHole(hole), () -> String.format("Hole [%d] must be 1 through 18", hole));
|
||||
|
||||
this.hole = hole;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Golfer shot(int score) {
|
||||
this.score = score;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client.model.support;
|
||||
|
||||
import example.app.caching.inline.async.client.model.GolfCourse;
|
||||
|
||||
/**
|
||||
* A {@literal Builder} class used to build {@link GolfCourse golf courses}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see example.app.caching.inline.async.client.model.GolfCourse
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public abstract class GolfCourseBuilder {
|
||||
|
||||
/**
|
||||
* Builds the {@literal Augusta National} {@link GolfCourse}, home of the {@literal Masters} major golf tournament.
|
||||
*
|
||||
* @return a new instance of {@link GolfCourse} modeling {@literal Augusta National} in Augusta, GA; USA.
|
||||
* @see example.app.caching.inline.async.client.model.GolfCourse
|
||||
*/
|
||||
public static GolfCourse buildAugustaNational() {
|
||||
|
||||
return GolfCourse.newGolfCourse("Augusta National")
|
||||
.withHole(1, 4)
|
||||
.withHole(2, 5)
|
||||
.withHole(3, 4)
|
||||
.withHole(4, 3)
|
||||
.withHole(5, 4)
|
||||
.withHole(6, 3)
|
||||
.withHole(7, 4)
|
||||
.withHole(8, 5)
|
||||
.withHole(9, 4)
|
||||
.withHole(10, 4)
|
||||
.withHole(11, 4)
|
||||
.withHole(12, 3)
|
||||
.withHole(13, 5)
|
||||
.withHole(14, 4)
|
||||
.withHole(15, 5)
|
||||
.withHole(16, 3)
|
||||
.withHole(17, 4)
|
||||
.withHole(18, 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client.model.support;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import example.app.caching.inline.async.client.model.Golfer;
|
||||
|
||||
/**
|
||||
* A {@literal Builder} class used to build {@link Golfer Golfers}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see example.app.caching.inline.async.client.model.Golfer
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public abstract class GolferBuilder {
|
||||
|
||||
public static final String[] FAVORITE_GOLFER_NAMES = {
|
||||
"Arnold Palmer",
|
||||
"Ben Hogan",
|
||||
"Bobby Jones",
|
||||
"Tiger Woods",
|
||||
"Rory McIlroy",
|
||||
"Dustin Johnson",
|
||||
"Jason Day",
|
||||
"Justin Thomas",
|
||||
"John Rahm",
|
||||
"Jordan Spieth",
|
||||
"Phil Michelson",
|
||||
"Ricky Fowler"
|
||||
};
|
||||
|
||||
public static Set<Golfer> buildGolfers(String... names) {
|
||||
|
||||
return Arrays.stream(ArrayUtils.nullSafeArray(names, String.class))
|
||||
.filter(StringUtils::hasText)
|
||||
.map(Golfer::newGolfer)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client.repo;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import example.app.caching.inline.async.client.model.Golfer;
|
||||
|
||||
/**
|
||||
* Spring Data {@link JpaRepository} and Data Access Object (DAO) used to perform basic CRUD and simple SQL query
|
||||
* data access operations on {@link Golfer Golfers} stored in an RDBMS (database) with JPA.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.jpa.repository.JpaRepository
|
||||
* @see example.app.caching.inline.async.client.model.Golfer
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface GolferRepository extends JpaRepository<Golfer, String> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client.service;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import example.app.caching.inline.async.client.model.GolfCourse;
|
||||
import example.app.caching.inline.async.client.model.GolfTournament;
|
||||
import example.app.caching.inline.async.client.model.Golfer;
|
||||
|
||||
/**
|
||||
* Spring {@link Service} class used to manage golf tournaments.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.io.Closeable
|
||||
* @see org.springframework.scheduling.annotation.Scheduled
|
||||
* @see org.springframework.stereotype.Service
|
||||
* @see example.app.caching.inline.async.client.model.GolfTournament
|
||||
* @see example.app.caching.inline.async.client.model.Golfer
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Service
|
||||
@SuppressWarnings("unused")
|
||||
public class GolfTournamentService implements Closeable {
|
||||
|
||||
protected static final int SCORE_DELTA_BOUND = 2;
|
||||
|
||||
private final GolferService golferService;
|
||||
|
||||
private volatile GolfTournament golfTournament;
|
||||
|
||||
private final Random random = new Random(System.currentTimeMillis());
|
||||
|
||||
public GolfTournamentService(GolferService golferService) {
|
||||
|
||||
Assert.notNull(golferService, "GolferService must not be null");
|
||||
|
||||
this.golferService = golferService;
|
||||
}
|
||||
|
||||
public Optional<GolfTournament> getGolfTournament() {
|
||||
return Optional.ofNullable(this.golfTournament);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.golfTournament = null;
|
||||
}
|
||||
|
||||
public GolfTournamentService manage(GolfTournament golfTournament) {
|
||||
|
||||
GolfTournament currentGolfTournament = this.golfTournament;
|
||||
|
||||
Assert.state(currentGolfTournament == null,
|
||||
() -> String.format("Can only manage 1 golf tournament at a time; currently managing [%s]",
|
||||
currentGolfTournament));
|
||||
|
||||
this.golfTournament = golfTournament;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Scheduled(fixedRate = 2500L)
|
||||
public void play() {
|
||||
|
||||
GolfTournament golfTournament = this.golfTournament;
|
||||
|
||||
if (golfTournament != null) {
|
||||
playHole(golfTournament);
|
||||
finish(golfTournament);
|
||||
}
|
||||
}
|
||||
|
||||
private void playHole(@NonNull GolfTournament golfTournament) {
|
||||
|
||||
GolfCourse golfCourse = golfTournament.getGolfCourse();
|
||||
|
||||
Set<Integer> occupiedHoles = new HashSet<>();
|
||||
|
||||
for (GolfTournament.Pairing pairing : golfTournament) {
|
||||
|
||||
int hole = pairing.playNextHole();
|
||||
|
||||
if (!occupiedHoles.contains(hole)) {
|
||||
if (golfCourse.isValidHoleNumber(hole)) {
|
||||
occupiedHoles.add(hole);
|
||||
pairing.setHole(hole);
|
||||
updateScore(this::calculateRunningScore, pairing.getPlayerOne());
|
||||
updateScore(this::calculateRunningScore, pairing.getPlayerTwo());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Golfer updateScore(@NonNull Function<Integer, Integer> scoreFunction, @NonNull Golfer player) {
|
||||
|
||||
player.setScore(scoreFunction.apply(player.getScore()));
|
||||
|
||||
this.golferService.update(player);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
private int calculateFinalScore(@Nullable Integer scoreRelativeToPar) {
|
||||
|
||||
int finalScore = scoreRelativeToPar != null ? scoreRelativeToPar : 0;
|
||||
|
||||
int parForCourse = getGolfTournament()
|
||||
.map(GolfTournament::getGolfCourse)
|
||||
.map(GolfCourse::getParForCourse)
|
||||
.orElse(GolfCourse.STANDARD_PAR_FOR_COURSE);
|
||||
|
||||
return parForCourse + finalScore;
|
||||
}
|
||||
|
||||
private int calculateRunningScore(@Nullable Integer currentScore) {
|
||||
|
||||
int runningScore = currentScore != null ? currentScore : 0;
|
||||
int scoreDelta = this.random.nextInt(SCORE_DELTA_BOUND);
|
||||
|
||||
scoreDelta *= this.random.nextBoolean() ? -1 : 1;
|
||||
|
||||
return runningScore + scoreDelta;
|
||||
}
|
||||
|
||||
private void finish(@NonNull GolfTournament golfTournament) {
|
||||
|
||||
if (golfTournament.isFinished()) {
|
||||
|
||||
GolfCourse golfCourse = golfTournament.getGolfCourse();
|
||||
|
||||
for (GolfTournament.Pairing pairing : golfTournament) {
|
||||
updateScore(this::calculateFinalScore, pairing.getPlayerOne());
|
||||
updateScore(this::calculateFinalScore, pairing.getPlayerTwo());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.client.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.cache.annotation.CachePut;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.util.RegionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import example.app.caching.inline.async.client.model.Golfer;
|
||||
import example.app.caching.inline.async.client.repo.GolferRepository;
|
||||
|
||||
/**
|
||||
* Spring {@link Service} class used to manage {@link Golfer Golfers}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.stereotype.Service
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see example.app.caching.inline.async.client.model.Golfer
|
||||
* @see example.app.caching.inline.async.client.repo.GolferRepository
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Service
|
||||
@SuppressWarnings("unused")
|
||||
public class GolferService {
|
||||
|
||||
private final GemfireTemplate golfersTemplate;
|
||||
|
||||
private final GolferRepository golferRepository;
|
||||
|
||||
public GolferService(@Qualifier("golfersTemplate") GemfireTemplate golfersTemplate,
|
||||
GolferRepository golferRepository) {
|
||||
|
||||
Assert.notNull(golfersTemplate, "GolfersTemplate must not be null");
|
||||
Assert.notNull(golferRepository, "GolferRepository must not be null");
|
||||
|
||||
this.golfersTemplate = golfersTemplate;
|
||||
this.golferRepository = golferRepository;
|
||||
}
|
||||
|
||||
@CachePut(cacheNames = "Golfers", key = "#golfer.name")
|
||||
public Golfer update(Golfer golfer) {
|
||||
return golfer;
|
||||
}
|
||||
|
||||
public List<Golfer> getAllGolfersFromCache() {
|
||||
|
||||
Map<String, Golfer> golferMap =
|
||||
nullSafeMap(this.golfersTemplate.getAll(resolveKeys(this.golfersTemplate.getRegion())));
|
||||
|
||||
return sort(new ArrayList<>(golferMap.values()));
|
||||
}
|
||||
|
||||
public List<Golfer> getAllGolfersFromDatabase() {
|
||||
return sort(this.golferRepository.findAll());
|
||||
}
|
||||
|
||||
private <KEY, VALUE> Map<KEY, VALUE> nullSafeMap(Map<KEY, VALUE> map) {
|
||||
return map != null ? map : Collections.emptyMap();
|
||||
}
|
||||
|
||||
private Set<String> resolveKeys(Region<String, ?> region) {
|
||||
return RegionUtils.isClient(region) ? region.keySetOnServer() : region.keySet();
|
||||
}
|
||||
|
||||
private <T extends Comparable<T>> List<T> sort(List<T> list) {
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.config;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.geode.cache.AsyncInlineCachingRegionConfigurer;
|
||||
|
||||
import example.app.caching.inline.async.client.model.Golfer;
|
||||
import example.app.caching.inline.async.client.repo.GolferRepository;
|
||||
|
||||
/**
|
||||
* Spring {@link Configuration} class used to configure {@literal Async Inline Caching}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.time.Duration
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.Profile
|
||||
* @see org.springframework.geode.cache.AsyncInlineCachingRegionConfigurer
|
||||
* @see example.app.caching.inline.async.client.model.Golfer
|
||||
* @see example.app.caching.inline.async.client.repo.GolferRepository
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class AsyncInlineCachingConfiguration {
|
||||
|
||||
private static final String GOLFERS_REGION_NAME = "Golfers";
|
||||
|
||||
@Bean
|
||||
@Profile("queue-batch-size")
|
||||
AsyncInlineCachingRegionConfigurer<Golfer, String> batchSizeAsyncInlineCachingConfigurer(
|
||||
@Value("${spring.geode.sample.async-inline-caching.queue.batch-size:4}") int queueBatchSize,
|
||||
GolferRepository golferRepository) {
|
||||
|
||||
return AsyncInlineCachingRegionConfigurer.create(golferRepository, GOLFERS_REGION_NAME)
|
||||
.withQueueBatchSize(queueBatchSize)
|
||||
.withQueueBatchTimeInterval(Duration.ofMinutes(60))
|
||||
.withQueueDispatcherThreadCount(1);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("queue-batch-time-interval")
|
||||
AsyncInlineCachingRegionConfigurer<Golfer, String> batchTimeIntervalAsyncInlineCachingConfigurer(
|
||||
@Value("${spring.geode.sample.async-inline-caching.queue.batch-time-interval-ms:5000}") int queueBatchTimeIntervalMilliseconds,
|
||||
GolferRepository golferRepository) {
|
||||
|
||||
return AsyncInlineCachingRegionConfigurer.create(golferRepository, GOLFERS_REGION_NAME)
|
||||
.withQueueBatchTimeInterval(Duration.ofMillis(queueBatchTimeIntervalMilliseconds))
|
||||
.withQueueBatchSize(1000000)
|
||||
.withQueueDispatcherThreadCount(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2020 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.async.server;
|
||||
|
||||
import org.apache.geode.cache.RegionShortcut;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.gemfire.config.annotation.CacheServerApplication;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
|
||||
|
||||
import example.app.caching.inline.async.client.model.Golfer;
|
||||
import example.app.caching.inline.async.config.AsyncInlineCachingConfiguration;
|
||||
|
||||
/**
|
||||
* {@link SpringBootApplication} class implementing the server-side of the golf tournament management application.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.time.Duration
|
||||
* @see org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
* @see org.springframework.boot.builder.SpringApplicationBuilder
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.context.annotation.Profile
|
||||
* @see org.springframework.data.gemfire.config.annotation.CacheServerApplication
|
||||
* @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions
|
||||
* @see example.app.caching.inline.async.config.AsyncInlineCachingConfiguration
|
||||
* @see example.app.caching.inline.async.client.model.Golfer
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@Profile("server")
|
||||
public class BootGeodeAsyncInlineCachingServerApplication {
|
||||
|
||||
private static final String APPLICATION_NAME = "GolfServerApplication";
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
new SpringApplicationBuilder(BootGeodeAsyncInlineCachingServerApplication.class)
|
||||
.web(WebApplicationType.NONE)
|
||||
.build()
|
||||
.run(args);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@CacheServerApplication(name = APPLICATION_NAME)
|
||||
@EnableEntityDefinedRegions(basePackageClasses = Golfer.class, serverRegionShortcut = RegionShortcut.LOCAL)
|
||||
@Import(AsyncInlineCachingConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
static class GeodeConfiguration { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Spring Boot application.properties configuration for the golf client application
|
||||
|
||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
|
||||
@@ -0,0 +1,5 @@
|
||||
# Spring Boot application.properties configuration for the golfer server application.
|
||||
# RBMS (Database) configuration properties.
|
||||
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE IF NOT EXISTS golfers (
|
||||
name VARCHAR(256) PRIMARY KEY,
|
||||
hole NUMERIC(10),
|
||||
score NUMERIC(10)
|
||||
);
|
||||
Reference in New Issue
Block a user