/** * 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 { if (!instances.containsKey(key)) { 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(); } } } } }