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

io.fabric8.kubernetes.client.informers.cache.Cache Maven / Gradle / Ivy

/**
 * Copyright (C) 2015 Red Hat, 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 io.fabric8.kubernetes.client.informers.cache;

import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.utils.ReflectUtils;
import io.fabric8.kubernetes.client.utils.Utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Function;

/**
 * It basically saves and indexes all the entries.
 *
 * @param  type for cache object
 */
public class Cache implements Indexer {
  // Defines how to map objects into indices
  private Function keyFunc;

  // NAMESPACE_INDEX is the default index function for caching objects
  public static final String NAMESPACE_INDEX = "namespace";

  // indexers stores index functions by their names
  private final Map>> indexers = new HashMap<>();

  // items stores object instances
  private volatile ConcurrentHashMap items = new ConcurrentHashMap<>();

  // indices stores objects' key by their indices
  private final Map>> indices = new HashMap<>();
  
  private BooleanSupplier isRunning = () -> false;

  public Cache() {
    this(NAMESPACE_INDEX, Cache::metaNamespaceIndexFunc, Cache::metaNamespaceKeyFunc);
  }

  public Cache(String indexName, Function> indexFunc, Function keyFunc) {
    this.indexers.put(indexName, indexFunc);
    this.keyFunc = keyFunc;
    this.indices.put(indexName, new HashMap<>());
  }
  
  public void setIsRunning(BooleanSupplier isRunning) {
    this.isRunning = isRunning;
  }

  /**
   * Returns the indexers registered with the cache.
   *
   * @return registered indexers
   */
  @Override
  public synchronized Map>> getIndexers() {
    return Collections.unmodifiableMap(indexers);
  }

  @Override
  public synchronized void addIndexers(Map>> indexersNew) {
    if (isRunning.getAsBoolean()) {
      throw new IllegalStateException("Cannot add indexers to a running informer.");
    }  
    if (!items.isEmpty()) {
      throw new IllegalStateException("Cannot add indexers to a Cache which is not empty");
    }

    Set intersection = new HashSet<>(indexers.keySet());
    intersection.retainAll(indexersNew.keySet());
    if (!intersection.isEmpty()) {
      throw new IllegalArgumentException("Indexer conflict: " + intersection);
    }

    for (Map.Entry>> indexEntry : indexersNew.entrySet()) {
      addIndexFunc(indexEntry.getKey(), indexEntry.getValue());
    }
  }

  /**
   * Update the object.
   *
   * @param obj the object
   * @return the old object
   */
  public synchronized T put(T obj) {
    if (obj == null) {
      return null;
    }
    String key = getKey(obj);
    T oldObj = this.items.put(key, obj);
    this.updateIndices(oldObj, obj, key);
    return oldObj;
  }

  /**
   * Delete the object.
   *
   * @param obj object
   * @return the old object
   */
  public synchronized T remove(T obj) {
    String key = getKey(obj);
    T old = this.items.remove(key);
    if (old != null) {
      this.deleteFromIndices(old, key);
    }
    return old;
  }

  /**
   * Replace the content in the cache completely.
   * 
   * Return a copy of the old cache contents
   *
   * @param list list of objects
   * @return the old contents
   */
  public synchronized Map replace(List list) {
    ConcurrentHashMap newItems = new ConcurrentHashMap<>();
    for (T item : list) {
      String key = getKey(item);
      newItems.put(key, item);
    }
    Map result = new HashMap<>(items);
    this.items = newItems;

    // rebuild any index
    this.indices.values().stream().forEach(Map::clear);
    for (Map.Entry itemEntry : items.entrySet()) {
      this.updateIndices(null, itemEntry.getValue(), itemEntry.getKey());
    }
    return result;
  }

  /**
   * List keys
   *
   * @return the list of keys
   */
  @Override
  public List listKeys() {
    return new ArrayList<>(this.items.keySet());
  }

  /**
   * Get object
   *
   * @param obj the object
   * @return the object
   */
  @Override
  public T get(T obj) {
    String key = getKey(obj);
    return this.getByKey(key);
  }

  /**
   * Get the key for the given object
   */
  public String getKey(T obj) {
    String result = this.keyFunc.apply(obj);
    return result == null ? "" : result;
  }

  /**
   * List all objects in the cache.
   *
   * @return the list
   */
  @Override
  public List list() {
    return new ArrayList<>(this.items.values());
  }

  /**
   * Gets get by key.
   *
   * @param key specific key
   * @return the get by key
   */
  @Override
  public T getByKey(String key) {
      return this.items.get(key);
  }

  /**
   * Get objects
   *
   * @param indexName specific indexing function
   * @param obj object
   * @return the list
   */
  @Override
  public synchronized List index(String indexName, T obj) {
    if (!this.indexers.containsKey(indexName)) {
      throw new IllegalArgumentException(String.format("index %s doesn't exist!", indexName));
    }
    Function> indexFunc = this.indexers.get(indexName);
    List indexKeys = indexFunc.apply(obj);
    Map> index = this.indices.get(indexName);
    if (index.isEmpty()) {
      return new ArrayList<>();
    }

    Set returnKeySet = new HashSet<>();
    for (String indexKey : indexKeys) {
      Set set = index.get(indexKey);
      if (set.isEmpty()) {
        continue;
      }
      returnKeySet.addAll(set);
    }

    List items = new ArrayList<>(returnKeySet.size());
    for (String absoluteKey : returnKeySet) {
      items.add(this.items.get(absoluteKey));
    }
    return items;
  }

  /**
   * Index keys list
   *
   * @param indexName specific indexing function
   * @param indexKey specific index key
   * @return the list
   */
  @Override
  public synchronized List indexKeys(String indexName, String indexKey) {
    if (!this.indexers.containsKey(indexName)) {
      throw new IllegalArgumentException(String.format("index %s doesn't exist!", indexName));
    }
    Map> index = this.indices.get(indexName);
    Set set = index.get(indexKey);
    List keys = new ArrayList<>(set.size());
    for (String key : set) {
      keys.add(key);
    }
    return keys;
  }

  /**
   * By index list
   *
   * @param indexName specific indexing function
   * @param indexKey specific index key
   * @return the list
   */
  @Override
  public synchronized List byIndex(String indexName, String indexKey) {
    if (!this.indexers.containsKey(indexName)) {
      throw new IllegalArgumentException(String.format("index %s doesn't exist!", indexName));
    }
    Map> index = this.indices.get(indexName);
    Set set = index.get(indexKey);
    if (set == null) {
      return Arrays.asList();
    }
    List items = new ArrayList<>(set.size());
    for (String key : set) {
      items.add(this.items.get(key));
    }
    return items;
  }

  /**
   * UpdateIndices modifies the objects location in the managed indexes, if there is
   * an update, you must provide an oldObj
   *
   *
   * @param oldObj old object
   * @param newObj new object
   * @param key the key
   */
  void updateIndices(T oldObj, T newObj, String key) {
    if (oldObj != null) {
      deleteFromIndices(oldObj, key);
    }

    for (Map.Entry>> indexEntry : indexers.entrySet()) {
      String indexName = indexEntry.getKey();
      Function> indexFunc = indexEntry.getValue();
      List indexValues = indexFunc.apply(newObj);
      if (indexValues == null || indexValues.isEmpty()) {
        continue;
      }

      Map> index = this.indices.get(indexName);
      for (String indexValue : indexValues) {
        Set indexSet = index.computeIfAbsent(indexValue, k -> new HashSet<>());
        indexSet.add(key);
      }
    }
  }

  /**
   * Removes the object from each of the managed indexes.
   *
   * It is intended to be called from a function that already has a lock on the cache.
   *
   * @param oldObj the old object
   * @param key the key
   */
  private void deleteFromIndices(T oldObj, String key) {
    for (Map.Entry>> indexEntry : this.indexers.entrySet()) {
      Function> indexFunc = indexEntry.getValue();
      List indexValues = indexFunc.apply(oldObj);
      if (indexValues == null || indexValues.isEmpty()) {
        continue;
      }

      Map> index = this.indices.get(indexEntry.getKey());
      if (index == null) {
        continue;
      }
      for (String indexValue : indexValues) {
        Set indexSet = index.get(indexValue);
        if (indexSet != null) {
          indexSet.remove(key);
        }
      }
    }
  }

  /**
   * Add index func.
   *
   * @param indexName the index name
   * @param indexFunc the index func
   */
  public synchronized void addIndexFunc(String indexName, Function> indexFunc) {
    this.indices.put(indexName, new HashMap<>());
    this.indexers.put(indexName, indexFunc);
  }

  /**
   * It's is a convenient default KeyFunc which know show to make keys for API
   * objects which implement HasMetadata interface. The key uses the format
   * namespace/name unless namespace is empty, then it's just name
   *
   * @param obj specific object
   * @return the key
   */
  public static String metaNamespaceKeyFunc(Object obj) {
    try {
      if( obj == null ) {
        return "";
      }
      ObjectMeta metadata;
      if(obj instanceof String) {
        return (String) obj;
      } else if (obj instanceof ObjectMeta) {
        metadata = (ObjectMeta) obj;
      } else {
        metadata = ReflectUtils.objectMetadata(obj);
        if (metadata == null) {
          throw new RuntimeException("Object is bad :" + obj);
        }
      }

      return namespaceKeyFunc(metadata.getNamespace(), metadata.getName());
    } catch (ReflectiveOperationException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Default index function that indexes based on an object's namespace and name.
   *
   * @see #metaNamespaceKeyFunc
   */
  public static String namespaceKeyFunc(String objectNamespace, String objectName) {
    if (Utils.isNullOrEmpty(objectNamespace)) {
      return objectName;
    }
    return objectNamespace + "/" + objectName;
  }

  /**
   * It is a default index function that indexes based on an object's namespace
   *
   * @param obj the specific object
   * @return the indexed value
   */
  public static List metaNamespaceIndexFunc(Object obj) {
    try {
      ObjectMeta metadata = ReflectUtils.objectMetadata(obj);
      return metadata == null ? Collections.emptyList() : Collections.singletonList(metadata.getNamespace());
    } catch (ReflectiveOperationException e) {
      throw new RuntimeException(e);
    }
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy