All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.mt.runtime.DbHealthIndicatorImpl Maven / Gradle / Ivy

There is a newer version: 3.3.3
Show newest version
/******************************************************************************
 * © 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