Commit 4d2101e6 authored by Valentin Apostolov's avatar Valentin Apostolov
Browse files

Upload Backend Services

parent 0fb4aec7
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
*.crt
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
/bin/
# eforms-gateway
**Description:**
Spring Boot application that is serving as API gateway and is secured with KeyCloak using OAuth 2.0.
---
**Prerequisites:**
You need to fulfill the following prerequisites in order to start the application:
- JDK 11
- Maven
- KeyCloak service running at http://localhost:9090
---
**How to start the application locally:**
1. Login at KeyCloak Admin Console (http://localhost:9090/auth/admin/master/console).
2. Select "Camunda" realm.
3. Import the configuration for API gateway client (/eforms-gateway/src/main/resources/keycloak/eforms-gateway.json) into Keycloak. As a result new client will be created - "eforms-gateway".
4. Click on "Clients" and select "eforms-gateway". In "Credentials" section click "Regenerate secret".
5. Update the configuration properties with the newly generated client secret.
```
##
# KeyCloak Security Configuration
##
keycloak.url.auth: ${KEYCLOAK_URL_AUTH:http://localhost:9090}
keycloak.url.token: ${KEYCLOAK_URL_TOKEN:http://localhost:9090}
keycloak.url.plugin: ${KEYCLOAK_URL_PLUGIN:http://localhost:9090}
keycloak.client.id: ${KEYCLOAK_CLIENT_ID:eforms-gateway}
keycloak.client.secret: ${KEYCLOAK_CLIENT_SECRET:<new_secret>}
```
\ No newline at end of file
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bulpros</groupId>
<artifactId>eForms-gateway</artifactId>
<version>1.4.0</version>
<name>eForms-gateway</name>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2020.0.2</spring-cloud.version>
<sonar.projectName>eForms-Gateway</sonar.projectName>
<sonar.projectKey>eForms-Gateway</sonar.projectKey>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>io.sixhours</groupId>
<artifactId>memcached-spring-boot-starter</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Spring Retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.8</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>com.bulpros</groupId>
<artifactId>formio-client</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>local</id>
<repositories>
<repository>
<id>central-repository</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
<repository>
<id>bulpros-nexus</id>
<url>http://10.191.35.200:8081/repository/bulpros/</url>
</repository>
</repositories>
<properties>
<spring.profiles.active>local</spring.profiles.active>
</properties>
</profile>
<profile>
<id>prod</id>
<repositories>
<repository>
<id>central-repository</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
<repository>
<id>bulpros-nexus</id>
<url>http://172.23.130.71:8081/repository/bulpros/</url>
</repository>
</repositories>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
package com.bulpros.eformsgateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.retry.annotation.EnableRetry;
@SpringBootApplication
@ConfigurationPropertiesScan("com.bulpros.eformsgateway")
@EnableRetry
public class EformsGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(EformsGatewayApplication.class, args);
}
}
package com.bulpros.eformsgateway.cache.service;
public interface CacheService {
public static final String PUBLIC_CACHE = "public";
public static final String NO_CACHE = "no-cache";
public static final String CACHE_ACTIVE_CONDITION = "#root.target.cacheService.isCacheActive(#root.caches[0].name)";
public static final String CACHE_CONTROL_CONDITION = "#root.target.cacheService.isCacheEnabled(#root.caches[0].name, #cacheControl)";
<T> T get(final String cacheName, final String key, final Class<T> type, final String cacheControl);
<T> T put(final String cacheName, final String key, final T value, final String cacheControl);
<T> T putIfPresent(final String cacheName, final String key, final T value, final String cacheControl);
<T> T putIfAbsent(final String cacheName, final String key, final T value, final String cacheControl);
void evict(String cacheName, String key);
boolean evictIfPresent(String cacheName, String key);
void invalidateCache(String cacheName);
void invalidateAllCaches();
boolean isCacheActive(final String cacheName);
boolean isCacheEnabled(final String cacheName, final String cacheControl);
}
package com.bulpros.eformsgateway.cache.service;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class CacheServiceImpl implements CacheService {
@Value("${active-caches}")
private final List<String> activeCaches;
private final CacheManager cacheManager;
@Override
@SuppressWarnings("unchecked")
public <T> T get(String cacheName, String key, Class<T> type, String cacheControl) {
var cache = getCache(cacheName);
if (isNull(cache.get(key)) || isNull(cache.get(key).get()) || !isCacheEnabled(cacheName, cacheControl))
return null;
return (T) cache.get(key).get();
}
@Override
public <T> T put(String cacheName, String key, T value, String cacheControl) {
if (isCacheEnabled(cacheName, cacheControl))
getCache(cacheName).put(key, value);
return value;
}
@Override
public <T> T putIfPresent(final String cacheName, final String key, final T value, String cacheControl) {
var cache = getCache(cacheName);
if (isNull(value) || isNull(cache.get(key)) || isNull(cache.get(key).get()))
return value;
return put(cacheName, key, value, cacheControl);
}
@Override
public <T> T putIfAbsent(final String cacheName, final String key, final T value, String cacheControl) {
if (isCacheEnabled(cacheName, cacheControl))
getCache(cacheName).putIfAbsent(key, value);
return value;
}
@Override
public void evict(final String cacheName, final String key) {
getCache(cacheName).evict(key);
}
/**
* Memcached implementation not support evictIfPresent method provided by Spring Interface.
*/
@Override
public boolean evictIfPresent(final String cacheName, final String key) {
final boolean isPresentInCache = nonNull(getCache(cacheName).get(key));
if (isPresentInCache) {
evict(cacheName, key);
}
return isPresentInCache;
}
@Override
public void invalidateCache(final String cacheName) {
getCache(cacheName).invalidate();
}
@Override
public void invalidateAllCaches() {
cacheManager.getCacheNames().forEach(this::invalidateCache);
}
@Override
public boolean isCacheActive(final String cacheName) {
return nonNull(activeCaches) && activeCaches.contains(cacheName);
}
@Override
public boolean isCacheEnabled(final String cacheName, final String cacheControl) {
return isCacheActive(cacheName) && (isNull(cacheControl) || !cacheControl.equalsIgnoreCase(NO_CACHE));
}
private Cache getCache(final String cacheName) {
return cacheManager.getCache(cacheName);
}
}
package com.bulpros.eformsgateway.cache.service.controller;
import com.bulpros.eformsgateway.cache.service.CacheService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/admin")
@RequiredArgsConstructor
public class CacheController {
private final CacheService cacheService;
@DeleteMapping(path = "/caches", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Void> invalidateCash() {
cacheService.invalidateAllCaches();
return ResponseEntity.ok().build();
}
}
package com.bulpros.eformsgateway.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class CacheConfig {
}
package com.bulpros.eformsgateway.config;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;
/**
* - Supports both HTTP and HTTPS
* - Uses a connection pool to re-use connections and save overhead of creating connections.
* - Has a custom connection keep-alive strategy (to apply a default keep-alive if one isn't specified)
* - Starts an idle connection monitor to continuously clean up stale connections.
*/
@Configuration
@EnableScheduling
public class HttpClientConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientConfig.class);
// Determines the timeout in milliseconds until a connection is established.
private static final int CONNECT_TIMEOUT = 30000;
// The timeout when requesting a connection from the connection manager.
private static final int REQUEST_TIMEOUT = 30000;
// The timeout for waiting for data
private static final int SOCKET_TIMEOUT = 60000;
private static final int MAX_TOTAL_CONNECTIONS = 50;
private static final int DEFAULT_KEEP_ALIVE_TIME_MILLIS = 20 * 1000;
private static final int CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS = 30;
@Bean(name="poolingHttpClientConnectionManager")
public PoolingHttpClientConnectionManager poolingConnectionManager() {
SSLContextBuilder builder = new SSLContextBuilder();
try {
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
} catch (NoSuchAlgorithmException | KeyStoreException e) {
LOGGER.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e);
}
SSLConnectionSocketFactory sslsf = null;
try {
sslsf = new SSLConnectionSocketFactory(builder.build());
} catch (KeyManagementException | NoSuchAlgorithmException e) {
LOGGER.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e);
}
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
.<ConnectionSocketFactory>create().register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
poolingConnectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
return poolingConnectionManager;
}
@Bean(name="connectionKeepAliveStrategy")
public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
HeaderElementIterator it = new BasicHeaderElementIterator
(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000;
}
}
return DEFAULT_KEEP_ALIVE_TIME_MILLIS;
}
};
}
@Bean(name="closeableHttpClient")
public CloseableHttpClient httpClient() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(REQUEST_TIMEOUT)
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT).build();
return HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(poolingConnectionManager())
.setKeepAliveStrategy(connectionKeepAliveStrategy())
.build();
}
@Bean(name="idleConnectionMonitor")
public Runnable idleConnectionMonitor(@Qualifier("poolingHttpClientConnectionManager") final PoolingHttpClientConnectionManager connectionManager) {
return new Runnable() {
@Override
@Scheduled(fixedDelay = 10000)
public void run() {
try {
if (connectionManager != null) {
LOGGER.trace("run IdleConnectionMonitor - Closing expired and idle connections...");
connectionManager.closeExpiredConnections();
connectionManager.closeIdleConnections(CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS, TimeUnit.SECONDS);
} else {
LOGGER.trace("run IdleConnectionMonitor - Http Client Connection manager is not initialised");
}
} catch (Exception e) {
LOGGER.error("run IdleConnectionMonitor - Exception occurred. msg={}, e={}", e.getMessage(), e);
}
}
};
}
}
\ No newline at end of file
package com.bulpros.eformsgateway.config;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinioConfiguration {
@Value("${com.bulpros.minio.url}")
private String minioUrl;