/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.netflix.eureka;

import java.util.HashMap;
import java.util.Map;

import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthIndicator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;

import static com.netflix.appinfo.InstanceInfo.InstanceStatus;

/**
 * A Eureka health checker, maps the application status into {@link InstanceStatus} that
 * will be propagated to Eureka registry.
 *
 * On each heartbeat Eureka performs the health check invoking registered
 * {@link HealthCheckHandler}. By default this implementation will perform aggregation of
 * all registered {@link HealthIndicator} through registered {@link HealthAggregator}.
 *
 * @author Jakub Narloch
 * @see HealthCheckHandler
 * @see HealthAggregator
 */
public class EurekaHealthCheckHandler
		implements HealthCheckHandler, ApplicationContextAware, InitializingBean {

	private static final Map<Status, InstanceInfo.InstanceStatus> STATUS_MAPPING = new HashMap<Status, InstanceInfo.InstanceStatus>() {
		{
			put(Status.UNKNOWN, InstanceStatus.UNKNOWN);
			put(Status.OUT_OF_SERVICE, InstanceStatus.OUT_OF_SERVICE);
			put(Status.DOWN, InstanceStatus.DOWN);
			put(Status.UP, InstanceStatus.UP);
		}
	};

	private final CompositeHealthIndicator healthIndicator;

	private ApplicationContext applicationContext;

	public EurekaHealthCheckHandler(HealthAggregator healthAggregator) {
		Assert.notNull(healthAggregator, "HealthAggregator must not be null");
		this.healthIndicator = new CompositeHealthIndicator(healthAggregator);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		final Map<String, HealthIndicator> healthIndicators = applicationContext
				.getBeansOfType(HealthIndicator.class);

		for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {

			// ignore EurekaHealthIndicator and flatten the rest of the composite
			// otherwise there is a never ending cycle of down. See gh-643
			if (entry.getValue() instanceof DiscoveryCompositeHealthIndicator) {
				DiscoveryCompositeHealthIndicator indicator = (DiscoveryCompositeHealthIndicator) entry
						.getValue();
				for (DiscoveryCompositeHealthIndicator.Holder holder : indicator
						.getHealthIndicators()) {
					if (!(holder.getDelegate() instanceof EurekaHealthIndicator)) {
						healthIndicator.addHealthIndicator(holder.getDelegate().getName(),
								holder);
					}
				}

			}
			else {
				healthIndicator.addHealthIndicator(entry.getKey(), entry.getValue());
			}
		}
	}

	@Override
	public InstanceStatus getStatus(InstanceStatus instanceStatus) {
		return getHealthStatus();
	}

	protected InstanceStatus getHealthStatus() {
		final Status status = getHealthIndicator().health().getStatus();
		return mapToInstanceStatus(status);
	}

	protected InstanceStatus mapToInstanceStatus(Status status) {
		if (!STATUS_MAPPING.containsKey(status)) {
			return InstanceStatus.UNKNOWN;
		}
		return STATUS_MAPPING.get(status);
	}

	protected CompositeHealthIndicator getHealthIndicator() {
		return healthIndicator;
	}

}

@@ -977,32 +977,111 @@
 ealthIndicator;%0A
+import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;%0A
 import org.sprin
@@ -1185,32 +1185,111 @@
 ealthIndicator;%0A
+import org.springframework.boot.actuate.health.HealthIndicatorRegistryFactory;%0A
 import org.sprin
@@ -2573,22 +2573,16 @@
 private 
-final 
 Composit
@@ -2665,16 +2665,134 @@
 ntext;%0A%0A
+%09private HealthIndicatorRegistryFactory healthIndicatorRegistryFactory;%0A%0A%09private HealthAggregator healthAggregator;%0A%0A
 %09public 
@@ -2926,16 +2926,138 @@
 null%22);%0A
+%09%09this.healthAggregator = healthAggregator;%0A%09%09this.healthIndicatorRegistryFactory = new HealthIndicatorRegistryFactory();%0A
 %09%09this.h
@@ -3102,16 +3102,21 @@
 dicator(
+this.
 healthAg
@@ -3123,16 +3123,58 @@
 gregator
+,%0A%09%09%09%09new DefaultHealthIndicatorRegistry()
 );%0A%09%7D%0A%0A%09
@@ -3518,16 +3518,88 @@
 .class);
+%0A%09%09Map%3CString, HealthIndicator%3E finalHealthIndicators = new HashMap%3C%3E();
 %0A%0A%09%09for 
@@ -4172,35 +4172,21 @@
 %7B%0A%09%09%09%09%09%09
-healthIndicator.add
+final
 HealthIn
@@ -4192,16 +4192,21 @@
 ndicator
+s.put
 (holder.
@@ -4233,17 +4233,9 @@
 e(),
-%0A%09%09%09%09%09%09%09%09
+ 
 hold
@@ -4276,27 +4276,13 @@
 %09%09%09%09
-healthIndicator.add
+final
 Heal
@@ -4292,16 +4292,21 @@
 ndicator
+s.put
 (entry.g
@@ -4342,16 +4342,185 @@
 %09%09%7D%0A%09%09%7D%0A
+%09%09this.healthIndicator = new CompositeHealthIndicator(healthAggregator,%0A%09%09%09%09healthIndicatorRegistryFactory%0A%09%09%09%09%09%09.createHealthIndicatorRegistry(finalHealthIndicators));%0A
 %09%7D%0A%0A%09@Ov

Fix health check handler so it is compatible with boot 2.2.0