Merge branch 'master' into bael-16656

This commit is contained in:
Josh Cummings
2019-10-26 15:37:05 -06:00
committed by GitHub
parent db85c8f275
commit 0be2175c89
20539 changed files with 1643630 additions and 0 deletions
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung.spring.cloud</groupId>
<artifactId>svc-rating</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>svc-rating</name>
<parent>
<artifactId>parent-boot-1</artifactId>
<groupId>com.baeldung</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<spring-cloud-dependencies.version>Dalston.RELEASE</spring-cloud-dependencies.version>
</properties>
</project>
@@ -0,0 +1,28 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import com.baeldung.spring.cloud.bootstrap.svcrating.rating.Rating;
import com.baeldung.spring.cloud.bootstrap.svcrating.rating.RatingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class DataLoader implements ApplicationRunner {
private RatingService ratingService;
@Autowired
public DataLoader(RatingService ratingService) {
this.ratingService = ratingService;
}
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
this.ratingService.createRating(new Rating(1L, 1));
this.ratingService.createRating(new Rating(1L, 2));
this.ratingService.createRating(new Rating(2L, 3));
this.ratingService.createRating(new Rating(2L, 4));
this.ratingService.createRating(new Rating(2L, 5));
}
}
@@ -0,0 +1,70 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
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.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.client.RestTemplate;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;
import zipkin.Span;
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@EnableTransactionManagement(order=Ordered.LOWEST_PRECEDENCE, mode=AdviceMode.ASPECTJ)
public class RatingServiceApplication {
@Autowired
private EurekaClient eurekaClient;
@Autowired
private SpanMetricReporter spanMetricReporter;
@Autowired
private ZipkinProperties zipkinProperties;
@Value("${spring.sleuth.web.skipPattern}")
private String skipPattern;
public static void main(String[] args) {
SpringApplication.run(RatingServiceApplication.class, args);
}
@Bean
public ZipkinSpanReporter makeZipkinSpanReporter() {
return new ZipkinSpanReporter() {
private HttpZipkinSpanReporter delegate;
private String baseUrl;
@Override
public void report(Span span) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("zipkin", false);
if (!(baseUrl != null && instance.getHomePageUrl().equals(baseUrl))) {
baseUrl = instance.getHomePageUrl();
delegate = new HttpZipkinSpanReporter(new RestTemplate(), baseUrl, zipkinProperties.getFlushInterval(), spanMetricReporter);
if (!span.name.matches(skipPattern)) delegate.report(span);
}
if (!span.name.matches(skipPattern)) delegate.report(span);
}
};
}
@Bean
@Primary
@Order(value=Ordered.HIGHEST_PRECEDENCE)
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
}
@@ -0,0 +1,39 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal1(AuthenticationManagerBuilder auth) throws Exception {
//try in memory auth with no users to support the case that this will allow for users that are logged in to go anywhere
auth.inMemoryAuthentication();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.regexMatchers("^/ratings\\?bookId.*$").authenticated()
.antMatchers(HttpMethod.POST,"/ratings").authenticated()
.antMatchers(HttpMethod.PATCH,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE,"/ratings/*").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/ratings").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/hystrix").authenticated()
.anyRequest().authenticated()
.and()
.httpBasic().and()
.csrf()
.disable();
}
}
@@ -0,0 +1,28 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
@Autowired
Environment properties;
@Bean
@Primary
public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(properties.getProperty("spring.redis.host","localhost"));
factory.setPort(properties.getProperty("spring.redis.port", Integer.TYPE,6379));
factory.afterPropertiesSet();
factory.setUsePool(true);
return factory;
}
}
@@ -0,0 +1,86 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Rating implements Serializable {
/**
*
*/
private static final long serialVersionUID = 3308900941650386473L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long bookId;
private int stars;
@Transient
private boolean fromCache;
@Transient
private Long cachedTS = -1L;
public Rating() {
}
public Rating(Long id, Long bookId, int stars) {
this.id = id;
this.bookId = bookId;
this.stars = stars;
}
public Rating(Long bookId, int stars) {
this.bookId = bookId;
this.stars = stars;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getBookId() {
return bookId;
}
public void setBookId(Long bookId) {
this.bookId = bookId;
}
public int getStars() {
return stars;
}
public void setStars(int stars) {
this.stars = stars;
}
public boolean isFromCache() {
return fromCache;
}
public void setFromCache(boolean fromCache) {
this.fromCache = fromCache;
}
public Long getCachedTS() {
return cachedTS;
}
public void setCachedTS(Long cachedTS) {
this.cachedTS = cachedTS;
}
}
@@ -0,0 +1,118 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Repository;
@Repository
public class RatingCacheRepository implements InitializingBean {
@Autowired
private JedisConnectionFactory cacheConnectionFactory;
private StringRedisTemplate redisTemplate;
private ValueOperations<String, String> valueOps;
private SetOperations<String, String> setOps;
private ObjectMapper jsonMapper;
public List<Rating> findCachedRatingsByBookId(Long bookId) {
return setOps.members("book-" + bookId)
.stream()
.map(rtId -> {
try {
return jsonMapper.readValue(valueOps.get(rtId), Rating.class);
} catch (IOException ex) {
return null;
}
})
.collect(Collectors.toList());
}
public Rating findCachedRatingById(Long ratingId) {
try {
return jsonMapper.readValue(valueOps.get("rating-" + ratingId), Rating.class);
} catch (IOException e) {
return null;
}
}
public List<Rating> findAllCachedRatings() {
List<Rating> ratings = null;
ratings = redisTemplate.keys("rating*")
.stream()
.map(rtId -> {
try {
return jsonMapper.readValue(valueOps.get(rtId), Rating.class);
} catch (IOException e) {
return null;
}
})
.collect(Collectors.toList());
return ratings;
}
public boolean createRating(Rating persisted) {
try {
valueOps.set("rating-" + persisted.getId(), jsonMapper.writeValueAsString(persisted));
setOps.add("book-" + persisted.getBookId(), "rating-" + persisted.getId());
return true;
} catch (JsonProcessingException ex) {
return false;
}
}
public boolean updateRating(Rating persisted) {
try {
valueOps.set("rating-" + persisted.getId(), jsonMapper.writeValueAsString(persisted));
return true;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return false;
}
public boolean deleteRating(Long ratingId) {
Rating toDel;
try {
toDel = jsonMapper.readValue(valueOps.get("rating-" + ratingId), Rating.class);
setOps.remove("book-" + toDel.getBookId(), "rating-" + ratingId);
redisTemplate.delete("rating-" + ratingId);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
@Override
public void afterPropertiesSet() throws Exception {
this.redisTemplate = new StringRedisTemplate(cacheConnectionFactory);
this.valueOps = redisTemplate.opsForValue();
this.setOps = redisTemplate.opsForSet();
jsonMapper = new ObjectMapper();
jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
}
@@ -0,0 +1,51 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/ratings")
public class RatingController {
@Autowired
private RatingService ratingService;
@GetMapping
public List<Rating> findRatingsByBookId(@RequestParam(required = false) Optional<Long> bookId) {
return bookId.map(ratingService::findRatingsByBookId)
.orElseGet(ratingService::findAllRatings);
}
@PostMapping
public Rating createRating(@RequestBody Rating rating) {
return ratingService.createRating(rating);
}
@DeleteMapping("/{ratingId}")
public void deleteRating(@PathVariable Long ratingId) {
ratingService.deleteRating(ratingId);
}
@PutMapping("/{ratingId}")
public Rating updateRating(@RequestBody Rating rating, @PathVariable Long ratingId) {
return ratingService.updateRating(rating, ratingId);
}
@PatchMapping("/{ratingId}")
public Rating updateRating(@RequestBody Map<String, String> updates, @PathVariable Long ratingId) {
return ratingService.updateRating(updates, ratingId);
}
}
@@ -0,0 +1,11 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
class RatingNotFoundException extends RuntimeException {
RatingNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,9 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
interface RatingRepository extends JpaRepository<Rating, Long>{
List<Rating> findRatingsByBookId(Long bookId);
}
@@ -0,0 +1,93 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.base.Preconditions;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@Service
@Transactional(readOnly = true)
public class RatingService {
@Autowired
private RatingRepository ratingRepository;
@Autowired
private RatingCacheRepository cacheRepository;
@HystrixCommand(commandKey = "ratingsByBookIdFromDB", fallbackMethod = "findCachedRatingsByBookId")
public List<Rating> findRatingsByBookId(Long bookId) {
return ratingRepository.findRatingsByBookId(bookId);
}
public List<Rating> findCachedRatingsByBookId(Long bookId) {
return cacheRepository.findCachedRatingsByBookId(bookId);
}
@HystrixCommand(commandKey = "ratingsFromDB", fallbackMethod = "findAllCachedRatings")
public List<Rating> findAllRatings() {
return ratingRepository.findAll();
}
public List<Rating> findAllCachedRatings() {
return cacheRepository.findAllCachedRatings();
}
@HystrixCommand(commandKey = "ratingsByIdFromDB", fallbackMethod = "findCachedRatingById", ignoreExceptions = { RatingNotFoundException.class })
public Rating findRatingById(Long ratingId) {
return Optional.ofNullable(ratingRepository.findOne(ratingId))
.orElseThrow(() -> new RatingNotFoundException("Rating not found. ID: " + ratingId));
}
public Rating findCachedRatingById(Long ratingId) {
return cacheRepository.findCachedRatingById(ratingId);
}
@Transactional(propagation = Propagation.REQUIRED)
public Rating createRating(Rating rating) {
Rating newRating = new Rating();
newRating.setBookId(rating.getBookId());
newRating.setStars(rating.getStars());
Rating persisted = ratingRepository.save(newRating);
cacheRepository.createRating(persisted);
return persisted;
}
@Transactional(propagation = Propagation.REQUIRED)
public void deleteRating(Long ratingId) {
ratingRepository.delete(ratingId);
cacheRepository.deleteRating(ratingId);
}
@Transactional(propagation = Propagation.REQUIRED)
public Rating updateRating(Map<String, String> updates, Long ratingId) {
final Rating rating = findRatingById(ratingId);
updates.keySet()
.forEach(key -> {
switch (key) {
case "stars":
rating.setStars(Integer.parseInt(updates.get(key)));
break;
}
});
Rating persisted = ratingRepository.save(rating);
cacheRepository.updateRating(persisted);
return persisted;
}
@Transactional(propagation = Propagation.REQUIRED)
public Rating updateRating(Rating rating, Long ratingId) {
Preconditions.checkNotNull(rating);
Preconditions.checkState(rating.getId() == ratingId);
Preconditions.checkNotNull(ratingRepository.findOne(ratingId));
return ratingRepository.save(rating);
}
}
@@ -0,0 +1,7 @@
spring.cloud.config.name=rating-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -0,0 +1,24 @@
package org.baeldung;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.spring.cloud.bootstrap.svcrating.RatingServiceApplication;
/**
*
* This Live Test requires:
* * A Redis instance running in port 6379 (e.g. using `docker run --name some-redis -p 6379:6379 -d redis`)
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RatingServiceApplication.class)
public class SpringContextLiveTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}
@@ -0,0 +1,2 @@
# This property would be provided by the config service in a real-case scenario
spring.sleuth.web.skipPattern=(^cleanup.|.+favicon.)