Skip to content

Commit

Permalink
Merge pull request #7 from OpenConext/develop
Browse files Browse the repository at this point in the history
Prepare release 2.2.0
  • Loading branch information
tbeekman authored Aug 3, 2023
2 parents 8e18473 + 33a7a3b commit d8ed1bf
Show file tree
Hide file tree
Showing 15 changed files with 403 additions and 24 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ jobs:
java-version: '17'
distribution: 'adopt'
cache: maven
- name: Fail build if version contains SNAPSHOT
run: |
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
if [[ "$VERSION" == *SNAPSHOT ]]; then
echo "Version contains SNAPSHOT, continuing the workflow."
else
echo "Version does not contain SNAPSHOT, failing the workflow!"
exit 1
fi
- name: Build with Maven
run: mvn -B -U clean verify install -P release -Djdk.net.URLClassPath.disableClassPathURLCheck=true
- run: echo "ARTIFACT_FILE_NAME=$(ls oc-sso-notificatie/target/*.jar | xargs basename | sed 's/\.[^.]*$//')" >> $GITHUB_ENV
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ jobs:
java-version: '17'
distribution: 'adopt'
cache: maven
- name: Fail build if version contains SNAPSHOT
run: |
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
if [[ "$VERSION" == *SNAPSHOT ]]; then
echo "Version contains SNAPSHOT, failing the workflow!"
exit 1
else
echo "Version is valid, continuing with the workflow."
fi
- name: Build with Maven and publish package
run: mvn -B -U clean verify install -P release deploy -Djdk.net.URLClassPath.disableClassPathURLCheck=true
env:
Expand Down
4 changes: 1 addition & 3 deletions oc-sso-notificatie/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<parent>
<artifactId>oc-sso-notificatie-parent</artifactId>
<groupId>nl.kennisnet.services</groupId>
<version>2.1.1</version>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand All @@ -34,8 +34,6 @@
<checkstyle.plugin.version>3.1.0</checkstyle.plugin.version>
<logback.encoder.version>5.3</logback.encoder.version>
<lombok.version>1.18.20</lombok.version>
<dependency.check.version>6.3.2</dependency.check.version>
<java.version>11</java.version>
<maven.deploy.skip>false</maven.deploy.skip>
</properties>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package nl.kennisnet.services.web.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

@Service
public class CacheHashService {

private static final Logger LOGGER = LoggerFactory.getLogger(CacheHashService.class);

@Value("${api.key.header.key}")
private String API_KEY_HEADER;

@Value("${api.key.header.value}")
private String apiKeyHeaderValue;

@Value("${api.endpoint.url.cacheHash:#{null}}")
private String url;

private final RestTemplate restTemplate;

public CacheHashService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

public String fetchCacheHash() {
if (null == url) {
LOGGER.info("Cache hash endpoint not set, returning empty hash");
return "";
}

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(API_KEY_HEADER, apiKeyHeaderValue);

HttpEntity<?> httpEntity = new HttpEntity<>(requestHeaders);

try {
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity,
new ParameterizedTypeReference<>() {});

String result = response.getBody();
if (null == result) {
LOGGER.warn("Received null from data-services cache-hash, returning empty hash");
return "";
}

return result;
} catch (HttpStatusCodeException htsce) {
LOGGER.error("Unexpected response received, returning empty hash. Error message: {}", htsce.getMessage());
return "";
} catch (RestClientException rce) {
LOGGER.error("Communication error occurred, returning empty hash. Error message: {} ", rce.getMessage());
return "";
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package nl.kennisnet.services.web.service.jobs;

import nl.kennisnet.services.web.config.CacheConfig;
import nl.kennisnet.services.web.service.CacheHashService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Component
public class DataServicesUpdateRunner implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(DataServicesUpdateRunner.class);

private static final String ALREADY_RUNNING = "DataServicesUpdateRunner is already running";

private static final String UPDATE_FAILED = "Cache evicts failed: {}";

private static final String UPDATE_FINISHED = "Cache evicts finished";

private static final Lock execLock = new ReentrantLock();

private static boolean running = false;

private static String lastDataServicesCacheHash = "";

private final CacheHashService cacheHashService;

private final CacheConfig cacheConfig;

public DataServicesUpdateRunner(CacheHashService cacheHashService, CacheConfig cacheConfig) {
this.cacheHashService = cacheHashService;
this.cacheConfig = cacheConfig;
}

@Override
@Scheduled(cron = "${dataservices.fetchCacheHash.cronSchedule:-}")
public void run() {
if (running || !execLock.tryLock()) {
LOGGER.warn(ALREADY_RUNNING);
return;
}

// Set the lock and execute the reaper
running = true;
// General exception to ensure that we do not create a deadlock for the scheduled runner
try {
execute();
} catch (Error | Exception e) {
LOGGER.error(UPDATE_FAILED, e.getMessage());
}

// Release the lock
running = false;
execLock.unlock();
}

private void execute() {
String dataServicesCacheHash = cacheHashService.fetchCacheHash();
if (dataServicesCacheHash.equals(lastDataServicesCacheHash)
|| (!lastDataServicesCacheHash.isEmpty() && dataServicesCacheHash.isEmpty())) {
return;
}

LOGGER.info("Changes since last update detected, evicting caches");
cacheConfig.cacheEvict();
lastDataServicesCacheHash = dataServicesCacheHash;

LOGGER.info(UPDATE_FINISHED);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package nl.kennisnet.services.web;

import nl.kennisnet.services.web.service.CacheHashService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

@SpringBootTest
public class CacheHashServiceTest {

@Mock
RestTemplate restTemplate;

@InjectMocks
CacheHashService cacheHashService;

@BeforeEach
void setUp() {
ReflectionTestUtils.setField(cacheHashService, "url", "sso-notification-url");
ReflectionTestUtils.setField(cacheHashService, "API_KEY_HEADER", "api-key");
}

@Test
void fetchCacheHashTest() {
when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class),
any(ParameterizedTypeReference.class))).thenReturn(new ResponseEntity<>("HASH", HttpStatus.OK));

String result = cacheHashService.fetchCacheHash();

assertEquals("HASH", result);
}

@Test
void fetchCacheNullReturnTest() {
when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class),
any(ParameterizedTypeReference.class))).thenReturn(new ResponseEntity<>(null, HttpStatus.OK));

String result = cacheHashService.fetchCacheHash();

assertEquals("", result);
}

@Test
void fetchCacheNullUrlTest() {
ReflectionTestUtils.setField(cacheHashService, "url", null);

when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class),
any(ParameterizedTypeReference.class))).thenReturn(new ResponseEntity<>(null, HttpStatus.OK));

String result = cacheHashService.fetchCacheHash();

assertEquals("", result);
}

@Test
void fetchCacheHashHttpExceptionTest() {
when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class),
any(ParameterizedTypeReference.class))).thenThrow(new HttpClientErrorException(HttpStatus.FORBIDDEN));

String result = cacheHashService.fetchCacheHash();

assertEquals("", result);
}

@Test
void fetchCacheRestClientExceptionTest() {
when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class),
any(ParameterizedTypeReference.class))).thenThrow(new RestClientException("ERROR"));

String result = cacheHashService.fetchCacheHash();

assertEquals("", result);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package nl.kennisnet.services.web.jobs;

import nl.kennisnet.services.web.config.CacheConfig;
import nl.kennisnet.services.web.service.CacheHashService;
import nl.kennisnet.services.web.service.jobs.DataServicesUpdateRunner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.util.ReflectionTestUtils;

import static org.mockito.Mockito.*;

@SpringBootTest
public class DataServicesUpdateRunnerTest {

@Mock
CacheHashService cacheHashService;

@Mock
CacheConfig cacheConfig;

@InjectMocks
DataServicesUpdateRunner dataServicesUpdateRunner;

@BeforeEach
void setup() {
ReflectionTestUtils.setField(dataServicesUpdateRunner, "lastDataServicesCacheHash", "HASH");
}

@Test
void runTest() {
ReflectionTestUtils.setField(dataServicesUpdateRunner, "running", false);

when(cacheHashService.fetchCacheHash()).thenReturn("NEW_HASH");

dataServicesUpdateRunner.run();

verify(cacheConfig, times(1)).cacheEvict();
}

@Test
void runSameHashTest() {
ReflectionTestUtils.setField(dataServicesUpdateRunner, "running", false);

when(cacheHashService.fetchCacheHash()).thenReturn("HASH");

dataServicesUpdateRunner.run();

verify(cacheConfig, times(0)).cacheEvict();
}

@Test
void runEmptyHashTest() {
ReflectionTestUtils.setField(dataServicesUpdateRunner, "running", false);

when(cacheHashService.fetchCacheHash()).thenReturn("");

dataServicesUpdateRunner.run();

verify(cacheConfig, times(0)).cacheEvict();
}

@Test
void canNotRunDouble() {
ReflectionTestUtils.setField(dataServicesUpdateRunner, "running", true);

dataServicesUpdateRunner.run();

verify(cacheConfig, times(0)).cacheEvict();
}

}
4 changes: 4 additions & 0 deletions oc-sso-notificatie/src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ api.key.header.key=api-key
api.key.header.value=TESTTOKEN
# The URL-suffix to fetch all SSO Notification data from the Data Service endpoint
api.endpoint.url.all-suffix=/all
# The URL-suffix to fetch the cache-hash to see if the cache needs to be evicted
api.endpoint.url.cacheHash=/cache-hash

# SSO Notification cookie settings
# The domain to set for the notification cookie
Expand All @@ -47,6 +49,8 @@ crypto.secure.salt=cQ_Jym5aH$Fqua#D
# Caching settings
# The expiration time in seconds to invalidate the cached entries for the retrieved idp information.
idp.cache.expiration.time.seconds=300
# Polling schedule if a central data-service is used, disabled by default
dataservices.fetchCacheHash.cronSchedule=-

# log level for whole application
logging.level.root=INFO
Expand Down
Loading

0 comments on commit d8ed1bf

Please sign in to comment.