/*
* Copyright 2012 Netflix, Inc.
*f
* 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 com.netflix.appinfo;import javax.inject.Inject;import javax.inject.Singleton;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import com.netflix.appinfo.InstanceInfo.InstanceStatus;import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider;import com.netflix.discovery.StatusChangeEvent;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/**
* The class that initializes information required for registration with
* <tt>Eureka Server</tt> and to be discovered by other components.
*
* <p>
* The information required for registration is provided by the user by passing
* the configuration defined by the contract in {@link EurekaInstanceConfig}
* }.AWS clients can either use or extend {@link CloudInstanceConfig
* }.Other non-AWS clients can use or extend either
* {@link MyDataCenterInstanceConfig} or very basic
* {@link AbstractInstanceConfig}.
* </p>
*
*
* @author Karthik Ranganathan, Greg Kim
*
*/@SingletonpublicclassApplicationInfoManager{privatestaticfinal Logger logger = LoggerFactory.getLogger(ApplicationInfoManager.class);privatestatic ApplicationInfoManager instance =newApplicationInfoManager();private InstanceInfo instanceInfo;private EurekaInstanceConfig config;protected Map<String, StatusChangeListener> listeners;privateApplicationInfoManager(){
listeners =newConcurrentHashMap<String, StatusChangeListener>();}/**
* public for spring DI use. This class should be in singleton scope so do not create explicitly.
* Either use DI or use getInstance().initComponent() if not using DI
*/@InjectpublicApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo){this.config = config;this.instanceInfo = instanceInfo;this.listeners =newConcurrentHashMap<String, StatusChangeListener>();// Hack to allow for getInstance() to use the DI'd ApplicationInfoManager
instance =this;}publicApplicationInfoManager(EurekaInstanceConfig config){this(config,newEurekaConfigBasedInstanceInfoProvider(config).get());}/**
* @deprecated please use DI instead
*/@Deprecatedpublicstatic ApplicationInfoManager getInstance(){return instance;}publicvoidinitComponent(EurekaInstanceConfig config){try{this.config = config;this.instanceInfo =newEurekaConfigBasedInstanceInfoProvider(config).get();}catch(Throwable e){thrownewRuntimeException("Failed to initialize ApplicationInfoManager", e);}}/**
* Gets the information about this instance that is registered with eureka.
*
* @return information about this instance that is registered with eureka.
*/public InstanceInfo getInfo(){return instanceInfo;}public EurekaInstanceConfig getEurekaInstanceConfig(){return config;}/**
* Register user-specific instance meta data. Application can send any other
* additional meta data that need to be accessed for other reasons.The data
* will be periodically sent to the eureka server.
*
* @param appMetadata
* application specific meta data.
*/publicvoidregisterAppMetadata(Map<String, String> appMetadata){
instanceInfo.registerRuntimeMetadata(appMetadata);}/**
* Set the status of this instance. Application can use this to indicate
* whether it is ready to receive traffic. Setting the status here also notifies all registered listeners
* of a status change event.
*
* @param status Status of the instance
*/publicsynchronizedvoidsetInstanceStatus(InstanceStatus status){
InstanceStatus prev = instanceInfo.setStatus(status);if(prev != null){for(StatusChangeListener listener : listeners.values()){try{
listener.notify(newStatusChangeEvent(prev, status));}catch(Exception e){
logger.warn("failed to notify listener: {}", listener.getId(), e);}}}}publicvoidregisterStatusChangeListener(StatusChangeListener listener){
listeners.put(listener.getId(), listener);}publicvoidunregisterStatusChangeListener(String listenerId){
listeners.remove(listenerId);}/**
* Refetches the hostname to check if it has changed. If it has, the entire
* <code>DataCenterInfo</code> is refetched and passed on to the eureka
* server on next heartbeat.
*
* see {@link InstanceInfo#getHostName()} for explanation on why the hostname is used as the default address
*/publicvoidrefreshDataCenterInfoIfRequired(){
String existingAddress = instanceInfo.getHostName();
String newAddress;if(config instanceofCloudInstanceConfig){// Refresh data center info, and return up to date address
newAddress =((CloudInstanceConfig) config).resolveDefaultAddress();}else{
newAddress = config.getHostName(true);}
String newIp = config.getIpAddress();if(newAddress != null &&!newAddress.equals(existingAddress)){
logger.warn("The address changed from : {} => {}", existingAddress, newAddress);// :( in the legacy code here the builder is acting as a mutator.// This is hard to fix as this same instanceInfo instance is referenced elsewhere.// We will most likely re-write the client at sometime so not fixing for now.
InstanceInfo.Builder builder =newInstanceInfo.Builder(instanceInfo);
builder.setHostName(newAddress).setIPAddr(newIp).setDataCenterInfo(config.getDataCenterInfo());
instanceInfo.setIsDirty();}}publicvoidrefreshLeaseInfoIfRequired(){
LeaseInfo leaseInfo = instanceInfo.getLeaseInfo();if(leaseInfo == null){return;}int currentLeaseDuration = config.getLeaseExpirationDurationInSeconds();int currentLeaseRenewal = config.getLeaseRenewalIntervalInSeconds();if(leaseInfo.getDurationInSecs()!= currentLeaseDuration || leaseInfo.getRenewalIntervalInSecs()!= currentLeaseRenewal){
LeaseInfo newLeaseInfo = LeaseInfo.Builder.newBuilder().setRenewalIntervalInSecs(currentLeaseRenewal).setDurationInSecs(currentLeaseDuration).build();
instanceInfo.setLeaseInfo(newLeaseInfo);
instanceInfo.setIsDirty();}}publicstaticinterfaceStatusChangeListener{
String getId();voidnotify(StatusChangeEvent statusChangeEvent);}}
@CommonsLogpublicclassInstanceInfoFactory{public InstanceInfo create(EurekaInstanceConfig config){
LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder().setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds()).setDurationInSecs(config.getLeaseExpirationDurationInSeconds());// Builder the instance information to be registered with eureka// server
InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
String namespace = config.getNamespace();if(!namespace.endsWith(".")){
namespace = namespace +".";}
builder.setNamespace(namespace).setAppName(config.getAppname()).setInstanceId(config.getInstanceId()).setAppGroupName(config.getAppGroupName()).setDataCenterInfo(config.getDataCenterInfo()).setIPAddr(config.getIpAddress()).setHostName(config.getHostName(false)).setPort(config.getNonSecurePort()).enablePort(InstanceInfo.PortType.UNSECURE,
config.isNonSecurePortEnabled()).setSecurePort(config.getSecurePort()).enablePort(InstanceInfo.PortType.SECURE, config.getSecurePortEnabled()).setVIPAddress(config.getVirtualHostName()).setSecureVIPAddress(config.getSecureVirtualHostName()).setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl()).setStatusPageUrl(config.getStatusPageUrlPath(),
config.getStatusPageUrl()).setHealthCheckUrls(config.getHealthCheckUrlPath(),
config.getHealthCheckUrl(), config.getSecureHealthCheckUrl()).setASGName(config.getASGName());// Start off with the STARTING state to avoid trafficif(!config.isInstanceEnabledOnit()){
InstanceInfo.InstanceStatus initialStatus = InstanceInfo.InstanceStatus.STARTING;if(log.isInfoEnabled()){
log.info("Setting initial instance status as: "+ initialStatus);}
builder.setStatus(initialStatus);}else{if(log.isInfoEnabled()){
log.info("Setting initial instance status as: "+ InstanceInfo.InstanceStatus.UP
+". This may be too early for the instance to advertise itself as available. "+"You would instead want to control this via a healthcheck handler.");}}// Add any user-specific metadata informationfor(Map.Entry<String, String> mapEntry : config.getMetadataMap().entrySet()){
String key = mapEntry.getKey();
String value = mapEntry.getValue();
builder.add(key, value);}
InstanceInfo instanceInfo = builder.build();
instanceInfo.setLeaseInfo(leaseInfoBuilder.build());return instanceInfo;}}