/*
* Copyright 2012 Netflix, Inc.
*
* 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.discovery.shared;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.InstanceRegionChecker;
import com.netflix.discovery.provider.Serializer;
import com.netflix.discovery.util.StringCache;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/**
* The application class holds the list of instances for a particular
* application.
*
* @author Karthik Ranganathan
*
*/
@Serializer("com.netflix.discovery.converters.EntityBodyConverter")
@XStreamAlias("application")
@JsonRootName("application")
public class Application {
@Override
public String toString() {
return "Application [name=" + name + ", isDirty=" + isDirty
+ ", instances=" + instances + ", shuffledInstances="
+ shuffledInstances + ", instancesMap=" + instancesMap + "]";
}
private String name;
@XStreamOmitField
private volatile boolean isDirty = false;
@XStreamImplicit
private final Set<InstanceInfo> instances;
private AtomicReference<List<InstanceInfo>> shuffledInstances = new AtomicReference<List<InstanceInfo>>();
private Map<String, InstanceInfo> instancesMap;
public Application() {
instances = new LinkedHashSet<InstanceInfo>();
instancesMap = new ConcurrentHashMap<String, InstanceInfo>();
}
public Application(String name) {
this.name = StringCache.intern(name);
instancesMap = new ConcurrentHashMap<String, InstanceInfo>();
instances = new LinkedHashSet<InstanceInfo>();
}
@JsonCreator
public Application(
@JsonProperty("name") String name,
@JsonProperty("instance") List<InstanceInfo> instances) {
this(name);
for (InstanceInfo instanceInfo : instances) {
addInstance(instanceInfo);
}
}
/**
* Add the given instance info the list.
*
* @param i
* the instance info object to be added.
*/
public void addInstance(InstanceInfo i) {
instancesMap.put(i.getId(), i);
synchronized (instances) {
instances.remove(i);
instances.add(i);
isDirty = true;
}
}
/**
* Remove the given instance info the list.
*
* @param i
* the instance info object to be removed.
*/
public void removeInstance(InstanceInfo i) {
removeInstance(i, true);
}
/**
* Gets the list of instances associated with this particular application.
* <p>
* Note that the instances are always returned with random order after
* shuffling to avoid traffic to the same instances during startup. The
* shuffling always happens once after every fetch cycle as specified in
* {@link EurekaClientConfig#getRegistryFetchIntervalSeconds}.
* </p>
*
* @return the list of shuffled instances associated with this application.
*/
@JsonProperty("instance")
public List<InstanceInfo> getInstances() {
if (this.shuffledInstances.get() == null) {
return this.getInstancesAsIsFromEureka();
} else {
return this.shuffledInstances.get();
}
}
/**
* Gets the list of non-shuffled and non-filtered instances associated with this particular
* application.
*
* @return list of non-shuffled and non-filtered instances associated with this particular
* application.
*/
@JsonIgnore
public List<InstanceInfo> getInstancesAsIsFromEureka() {
synchronized (instances) {
return new ArrayList<InstanceInfo>(this.instances);
}
}
/**
* Get the instance info that matches the given id.
*
* @param id
* the id for which the instance info needs to be returned.
* @return the instance info object.
*/
public InstanceInfo getByInstanceId(String id) {
return instancesMap.get(id);
}
/**
* Gets the name of the application.
*
* @return the name of the application.
*/
public String getName() {
return name;
}
/**
* Sets the name of the application.
*
* @param name
* the name of the application.
*/
public void setName(String name) {
this.name = StringCache.intern(name);
}
/**
* @return the number of instances in this application
*/
public int size() {
return instances.size();
}
/**
* Shuffles the list of instances in the application and stores it for
* future retrievals.
*
* @param filterUpInstances
* indicates whether only the instances with status
* {@link InstanceStatus#UP} needs to be stored.
*/
public void shuffleAndStoreInstances(boolean filterUpInstances) {
_shuffleAndStoreInstances(filterUpInstances, false, null, null, null);
}
public void shuffleAndStoreInstances(Map<String, Applications> remoteRegionsRegistry,
EurekaClientConfig clientConfig, InstanceRegionChecker instanceRegionChecker) {
_shuffleAndStoreInstances(clientConfig.shouldFilterOnlyUpInstances(), true, remoteRegionsRegistry, clientConfig,
instanceRegionChecker);
}
private void _shuffleAndStoreInstances(boolean filterUpInstances, boolean indexByRemoteRegions,
@Nullable Map<String, Applications> remoteRegionsRegistry,
@Nullable EurekaClientConfig clientConfig,
@Nullable InstanceRegionChecker instanceRegionChecker) {
List<InstanceInfo> instanceInfoList;
synchronized (instances) {
instanceInfoList = new ArrayList<InstanceInfo>(instances);
}
if (indexByRemoteRegions || filterUpInstances) {
Iterator<InstanceInfo> it = instanceInfoList.iterator();
while (it.hasNext()) {
InstanceInfo instanceInfo = it.next();
if (filterUpInstances && !InstanceStatus.UP.equals(instanceInfo.getStatus())) {
it.remove();
} else if (indexByRemoteRegions && null != instanceRegionChecker && null != clientConfig
&& null != remoteRegionsRegistry) {
String instanceRegion = instanceRegionChecker.getInstanceRegion(instanceInfo);
if (!instanceRegionChecker.isLocalRegion(instanceRegion)) {
Applications appsForRemoteRegion = remoteRegionsRegistry.get(instanceRegion);
if (null == appsForRemoteRegion) {
appsForRemoteRegion = new Applications();
remoteRegionsRegistry.put(instanceRegion, appsForRemoteRegion);
}
Application remoteApp =
appsForRemoteRegion.getRegisteredApplications(instanceInfo.getAppName());
if (null == remoteApp) {
remoteApp = new Application(instanceInfo.getAppName());
appsForRemoteRegion.addApplication(remoteApp);
}
remoteApp.addInstance(instanceInfo);
this.removeInstance(instanceInfo, false);
it.remove();
}
}
}
}
Collections.shuffle(instanceInfoList);
this.shuffledInstances.set(instanceInfoList);
}
private void removeInstance(InstanceInfo i, boolean markAsDirty) {
instancesMap.remove(i.getId());
synchronized (instances) {
instances.remove(i);
if (markAsDirty) {
isDirty = true;
}
}
}
}