content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.apache.ambari.view.utils;
  
  import org.apache.ambari.view.ViewContext;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.locks.ReentrantLock;
  
  /**
   * Manages end user specific objects.
   * Ensures that the instance of specific class is exists only in one instance for
   * specific user of specific instance.
   * @param <T> user-local class
   */
  public class UserLocal<T> {
    private final static Logger LOG =
      LoggerFactory.getLogger(UserLocal.class);
  
    private final static Map<Class, Map<String, Object>> viewSingletonObjects = new ConcurrentHashMap<>();
    private final Class<? extends T> tClass;
    private final static Map<String,ReentrantLock> locks = new HashMap<>();
  
    public UserLocal(Class<? extends T> tClass) {
      this.tClass =tClass;
    }
  
    /**
     * Initial value of user-local class. Value can be set either by initialValue() on first get() call,
     * or directly by calling set() method. Default initial value is null, it can be changed by overriding
     * this method.
     * @param context initial value usually based on user properties provided by View Context
     * @return initial value of user-local variable
     */
    protected synchronized T initialValue(ViewContext context) {
      return null;
    }
  
    /**
     * gives the lock object for the specified key. creates if not yet created.
     * @param key : the key for which the lock is required.
     * @return : ReentrantLock for that key
     */
    private static ReentrantLock getLockFor(String key){
      LOG.info("Finding lock for : {}", key);
      if( null == locks.get(key)){
        LOG.info("Lock not found for {} ",key);
        synchronized (locks){
          if(null == locks.get(key)){
            LOG.info("Creating lock for {} ", key);
            locks.put(key,new ReentrantLock());
          }
        }
      }
  
      return locks.get(key);
    }
  
    /**
     * Returns user-local instance.
     * If instance of class is not present yet for user, calls initialValue to create it.
     * @param context View context that provides instance and user names.
     * @return instance
     */
    public T get(ViewContext context) {
      if (!viewSingletonObjects.containsKey(tClass)) {
        synchronized (viewSingletonObjects) {
          if (!viewSingletonObjects.containsKey(tClass)) {
            viewSingletonObjects.put(tClass, new ConcurrentHashMap<String, Object>());
          }
        }
      }
  
      Map<String, Object> instances = viewSingletonObjects.get(tClass);
  
      String key = getTagName(context);
  
      LOG.debug("looking for key : {}", key);
      if (!instances.containsKey(key)) {
        String lockKey = tClass.getName() + "_" + key;
        LOG.info("key {} not found. getting lock for {}", key,lockKey);
  
        ReentrantLock lock = getLockFor(lockKey);
        boolean gotLock = lock.tryLock();
  
        if( !gotLock ){
          LOG.error("Lock could not be obtained for {}. Throwing exception.",lockKey);
          throw new RuntimeException(String.format("Failed to initialize %s for %s. Try Again.", tClass.getName(), key));
        }
        else {
          try {
            T initValue = initialValue(context);
            LOG.info("Obtained initial value : {} for key : {}",initValue,key);
            instances.put(key, initValue);
          }finally{
            lock.unlock();
          }
        }
      }
  
      return (T) instances.get(key);
    }
  
    /**
     * Method for directly setting user-local singleton instances.
     * @param obj new variable value for current user
     * @param context ViewContext that provides username and instance name
     */
    public void set(T obj, ViewContext context) {
      if (!viewSingletonObjects.containsKey(tClass)) {
        synchronized (viewSingletonObjects) {
          if (!viewSingletonObjects.containsKey(tClass)) {
            viewSingletonObjects.put(tClass, new ConcurrentHashMap<String, Object>());
          }
        }
      }
      String key = getTagName(context);
      LOG.info("setting key : value {} : {}", key,obj );
      Map<String, Object> instances = viewSingletonObjects.get(tClass);
      instances.put(key, obj);
    }
  
    /**
     * Remove instance if it's already exists.
     * @param context ViewContext that provides username and instance name
     */
    public void remove(ViewContext context) {
      String key = getTagName(context);
      LOG.info("removing key : {}", key);
      Map<String, Object> instances = viewSingletonObjects.get(tClass);
      if( null != instances ){
        instances.remove(key);
      }
    }
  
    /**
     * Returns unique key for Map to store a user-local variable.
     * @param context ViewContext
     * @return Unique identifier of pair instance-user.
     */
    private String getTagName(ViewContext context) {
      if (context == null) {
        return "<null>";
      }
      return String.format("%s:%s", context.getInstanceName(), context.getUsername());
    }
  
    /**
     * Method for testing purposes, intended to clear the cached user-local instances.
     * Method should not normally be called from production code.
     * @param tClass classname instances of which should be dropped
     */
    public static void dropAllConnections(Class tClass) {
      LOG.info("removing all {} " ,tClass.getName());
      Map<String, Object> instances = viewSingletonObjects.get(tClass);
      if (instances != null) {
        instances.clear();
      }
    }
  
    /**
     * Method for testing purposes, intended to clear the cached user-local instances.
     * Drops all classes of user-local variables.
     * Method should not normally be called from production code.
     */
    public static void dropAllConnections() {
        LOG.info("clearing all viewSingletonObjects.");
        viewSingletonObjects.clear();
    }
  
    /**
     *
     * Drops all objects for give instance name.
     *
     * @param instanceName : the name of the view instance for which the keys needs to be dropped.
     */
    public static void dropInstanceCache(String instanceName){
      LOG.info("removing all the keys for instanceName : {}", instanceName);
      for(Map<String,Object> cache : viewSingletonObjects.values()){
        for(Iterator<Map.Entry<String, Object>> it = cache.entrySet().iterator();it.hasNext();){
          Map.Entry<String, Object> entry = it.next();
          if(entry.getKey().startsWith(instanceName+":")){
            LOG.debug("removing key : {} ",entry.getKey());
            it.remove();
          }
        }
      }
    }
  }
  