com.sap.cloud.mt.runtime.DbHealthIndicatorImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of multi-tenant-runtime Show documentation
Show all versions of multi-tenant-runtime Show documentation
Spring Boot Enablement Parent
/******************************************************************************
* © 2020 SAP SE or an SAP affiliate company. All rights reserved. *
******************************************************************************/
package com.sap.cloud.mt.runtime;
import com.sap.cloud.mt.subscription.DataSourceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
public class DbHealthIndicatorImpl {
private static final Logger logger = LoggerFactory.getLogger(DbHealthIndicatorImpl.class);
private static final int STACK_TRACE_WRITE_PERIOD = 100;
private final String healthDummySelect;
private final TenantAwareDataSource dataSource;
private final ConnectionChecker connectionChecker;
private volatile long lastChecked = 0;
private volatile T lastHealth = null;
private final Long healthCheckIntervalMillis;
private final HealthUp healthUp;
private final HealthDown healthDown;
private final HealthDownDetails healthDownDetails;
private final HealthUpDetails healthUpDetails;
private final boolean newCheck;
private volatile int callCounter = 0;
private final ReentrantLock lock = new ReentrantLock();
public DbHealthIndicatorImpl(String healthDummySelect, TenantAwareDataSource dataSource,
Long healthCheckIntervalMillis,
HealthUp healthUp, HealthDown healthDown, HealthDownDetails healthDownDetails,
HealthUpDetails healthUpDetails,
boolean newCheck) {
this.healthDummySelect = healthDummySelect;
this.dataSource = dataSource;
this.connectionChecker = new ConnectionChecker();
this.healthCheckIntervalMillis = healthCheckIntervalMillis;
this.healthUp = healthUp;
this.healthDown = healthDown;
this.healthDownDetails = healthDownDetails;
this.healthUpDetails = healthUpDetails;
this.newCheck = newCheck;
}
public DbHealthIndicatorImpl(String healthDummySelect, TenantAwareDataSource dataSource,
Long healthCheckIntervalMillis,
HealthUp healthUp, HealthDown healthDown, HealthDownDetails healthDownDetails) {
this(healthDummySelect, dataSource, healthCheckIntervalMillis, healthUp, healthDown, healthDownDetails,
null, false);
}
public T health() {
boolean hasLock = false;
try {
hasLock = lock.tryLock();
if (!hasLock) {
if (lastHealth == null) {
return healthUp.execute();
}
return lastHealth;
}
callCounter++;
if (callCounter > STACK_TRACE_WRITE_PERIOD) {
callCounter = 0;
}
if (lastChecked != 0 && ((System.currentTimeMillis() - lastChecked) < healthCheckIntervalMillis)) {
return lastHealth;
}
try {
List detailInfo = new ArrayList<>();
boolean down = false;
if (newCheck) {
down = isDownNewCheck(detailInfo);
} else {
Map> infoPerDb = dataSourceInfoPerDb(dataSource.getDataSourceLookup().getCachedDataSource());
down = isDown(detailInfo, infoPerDb);
}
if (down) {
lastHealth = healthDownDetails.execute("Detail information:", detailInfo);
} else {
lastHealth = healthUpDetails != null ?
healthUpDetails.execute("Detail information:", detailInfo) : healthUp.execute();
}
} catch(RuntimeException e) {
lastHealth = healthDown.execute();
}
lastChecked = System.currentTimeMillis();
return lastHealth;
} finally {
if (hasLock) {
lock.unlock();
}
}
}
private boolean isDown(List detailInfo, Map> uriToDbInfo) {
Boolean[] downArray = {false};
uriToDbInfo.entrySet().forEach(infoListEntry -> {
List infoList = infoListEntry.getValue();
for (DataSourceInfo info : infoList) {
try {
connectionChecker.checkConnection(info.getTenantId(), dataSource, healthDummySelect);
detailInfo.add("Connection for DB " + info.getHost() + ":" + info.getPort() + " is ok");
//This DB is ok
break;
} catch(SQLException e) {
if (dataSource.doesTenantExist(info.getTenantId())) {
logger.error("Could not open connection for DB {}", info.getHost() + ":" + info.getPort());
logger.debug("The following error was reported: {}", e.getMessage());
detailInfo.add("Could not open connection for DB " + info.getHost() + ":" + info.getPort());
downArray[0] = true;
//problem with DB or pool
break;
} else {
//tenant was unsubscribed, this doesn't mean that the DB is broken
continue;
}
}
}
});
return downArray[0];
}
private boolean isDownNewCheck(List detailInfo) {
List healthCheckResults = dataSource.getDataSourceLookup().checkDataSourcePerDb(healthDummySelect);
Boolean[] down = {false};
healthCheckResults.stream().forEach(result -> {
if (!result.isOk()) {
logger.error("Could not open connection for DB {}", result.getDbIdentifier());
if (callCounter == STACK_TRACE_WRITE_PERIOD) {
logger.error("The following error was reported: ", result.getException());
} else {
logger.error("The following error was reported: {}", result.getException().getMessage());
}
detailInfo.add("Could not open connection for DB " + result.getDbIdentifier() + ". Error is: " +
result.getException().getMessage());
down[0] = true;
} else {
logger.debug("Connection for DB {} is ok", result.getDbIdentifier());
detailInfo.add("Connection for DB " + result.getDbIdentifier() + " is ok");
}
});
return down[0];
}
public String getHealthDummySelect() {
return healthDummySelect;
}
private Map> dataSourceInfoPerDb(List infoList) {
Map> urlToInfo = new HashMap<>();
infoList.stream().forEach(i -> {
List list = urlToInfo.get(DbUtils.getDbKey(i));
if (list == null) {
list = new ArrayList<>();
urlToInfo.put(DbUtils.getDbKey(i), list);
}
list.add(i);
});
return urlToInfo;
}
@FunctionalInterface
public interface HealthUp {
public T execute();
}
@FunctionalInterface
public interface HealthDown {
public T execute();
}
@FunctionalInterface
public interface HealthDownDetails {
public T execute(String text, List detailInfo);
}
@FunctionalInterface
public interface HealthUpDetails {
public T execute(String text, List detailInfo);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy