From 4a51a8f96715ffb2a42189b93b9fa91b453b8530 Mon Sep 17 00:00:00 2001 From: sg481n Date: Thu, 3 Aug 2017 17:39:12 -0400 Subject:  [AAF-21] Initial code import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Ia1dd196befd061f6ba0c2be6bf4456a30ea50f97 Signed-off-by: sg481n --- core/src/main/java/com/att/cadi/AbsUserCache.java | 409 ++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 core/src/main/java/com/att/cadi/AbsUserCache.java (limited to 'core/src/main/java/com/att/cadi/AbsUserCache.java') diff --git a/core/src/main/java/com/att/cadi/AbsUserCache.java b/core/src/main/java/com/att/cadi/AbsUserCache.java new file mode 100644 index 0000000..0229a71 --- /dev/null +++ b/core/src/main/java/com/att/cadi/AbsUserCache.java @@ -0,0 +1,409 @@ +/******************************************************************************* + * ============LICENSE_START==================================================== + * * org.onap.aai + * * =========================================================================== + * * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * * Copyright © 2017 Amdocs + * * =========================================================================== + * * 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. + * * ============LICENSE_END==================================================== + * * + * * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * * + ******************************************************************************/ +package com.att.cadi; + + +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +import com.att.cadi.Access.Level; +import com.att.cadi.CachedPrincipal.Resp; + +/** + * Implement Fast lookup and Cache for Local User Info + * + * Include ability to add and remove Users + * + * Also includes a Timer Thread (when necessary) to invoke cleanup on expiring Credentials + * + * + */ +public abstract class AbsUserCache { + static final int MIN_INTERVAL = 15000; + static final int MAX_INTERVAL = 1000*60*5; // 5 mins + private static Timer timer; + // Map of userName to User + private final Map> userMap; + private final Map missMap; + private Clean clean; + protected Access access; +// private final static Permission teaser = new LocalPermission("***NoPERM****"); + + protected AbsUserCache(Access access, long cleanInterval, int highCount, int usageCount) { + this.access = access; + userMap = new ConcurrentHashMap>(); + missMap = new TreeMap(); + if(cleanInterval>0) { + cleanInterval = Math.max(MIN_INTERVAL, cleanInterval); + synchronized(AbsUserCache.class) { // Lazy instantiate.. in case there is no cleanup needed + if(timer==null) { + timer = new Timer("CADI Cleanup Timer",true); + } + + timer.schedule(clean = new Clean(access, cleanInterval, highCount, usageCount), cleanInterval, cleanInterval); + access.log(Access.Level.INIT, "Cleaning Thread initialized with interval of",cleanInterval, "ms and max objects of", highCount); + } + } + } + + @SuppressWarnings("unchecked") + public AbsUserCache(AbsUserCache cache) { + this.access = cache.access; + userMap = cache.userMap; + missMap = cache.missMap; + synchronized(AbsUserCache.class) { + if(cache.clean!=null && cache.clean.lur==null && this instanceof CachingLur) { + cache.clean.lur=(CachingLur)this; + } + } + } + + protected void setLur(CachingLur lur) { + if(clean!=null)clean.lur = lur; + + } + + protected void addUser(User user) { + userMap.put(user.principal.getName(), user); + } + + // Useful for looking up by WebToken, etc. + protected void addUser(String key, User user) { + userMap.put(key, user); + } + + /** + * Add miss to missMap. If Miss exists, or too many tries, returns false. + * + * otherwise, returns true to allow another attempt. + * + * @param key + * @param bs + * @return + */ + protected boolean addMiss(String key, byte[] bs) { + Miss miss = missMap.get(key); + if(miss==null) { + synchronized(missMap) { + missMap.put(key, new Miss(bs,clean==null?MIN_INTERVAL:clean.timeInterval)); + } + return true; + } + return miss.add(bs); + } + + protected Miss missed(String key) { + return missMap.get(key); + } + + protected User getUser(String userName) { + User u = userMap.get(userName); + if(u!=null) { + u.incCount(); + } + return u; + } + + protected User getUser(Principal principal) { + return getUser(principal.getName()); + } + + /** + * Removes User from the Cache + * @param user + */ + protected void remove(User user) { + userMap.remove(user.principal.getName()); + } + + /** + * Removes user from the Cache + * + * @param user + */ + public void remove(String user) { + Object o = userMap.remove(user); + if(o!=null) { + access.log(Level.INFO, user,"removed from Client Cache by Request"); + } + } + + /** + * Clear all users from the Client Cache + */ + public void clearAll() { + userMap.clear(); + } + + public final List dumpInfo() { + List rv = new ArrayList(); + for(User user : userMap.values()) { + rv.add(new DumpInfo(user)); + } + return rv; + } + + /** + * The default behavior of a LUR is to not handle something exclusively. + */ + public boolean handlesExclusively(Permission pond) { + return false; + } + + /** + * Container calls when cleaning up... + * + * If overloading in Derived class, be sure to call "super.destroy()" + */ + public void destroy() { + if(timer!=null) { + timer.purge(); + timer.cancel(); + } + } + + + + // Simple map of Group name to a set of User Names + // private Map> groupMap = new HashMap>(); + + /** + * Class to hold a small subset of the data, because we don't want to expose actual Permission or User Objects + */ + public final class DumpInfo { + public String user; + public List perms; + + public DumpInfo(User user) { + this.user = user.principal.getName(); + perms = new ArrayList(user.perms.keySet()); + } + } + + /** + * Clean will examine resources, and remove those that have expired. + * + * If "highs" have been exceeded, then we'll expire 10% more the next time. This will adjust after each run + * without checking contents more than once, making a good average "high" in the minimum speed. + * + * + */ + private final class Clean extends TimerTask { + private final Access access; + private CachingLur lur; + + // The idea here is to not be too restrictive on a high, but to Expire more items by + // shortening the time to expire. This is done by judiciously incrementing "advance" + // when the "highs" are exceeded. This effectively reduces numbers of cached items quickly. + private final int high; + private long advance; + private final long timeInterval; + private final int usageTriggerCount; + + public Clean(Access access, long cleanInterval, int highCount, int usageTriggerCount) { + this.access = access; + lur = null; + high = highCount; + timeInterval = cleanInterval; + advance = 0; + this.usageTriggerCount=usageTriggerCount; + } + public void run() { + int renewed = 0; + int count = 0; + int total = 0; + try { + // look at now. If we need to expire more by increasing "now" by "advance" + ArrayList> al = new ArrayList>(userMap.values().size()); + al.addAll(0, userMap.values()); + long now = System.currentTimeMillis() + advance; + for(User user : al) { + ++total; + if(user.count>usageTriggerCount) { + // access.log(Level.AUDIT, "Checking Thread", new Date(now)); + boolean touched = false, removed=false; + if(user.principal instanceof CachedPrincipal) { + CachedPrincipal cp = (CachedPrincipal)user.principal; + if(cp.expires() < now) { + switch(cp.revalidate()) { + case INACCESSIBLE: + access.log(Level.AUDIT, "AAF Inaccessible. Keeping credentials"); + break; + case REVALIDATED: + user.resetCount(); + // access.log(Level.AUDIT, "CACHE revalidated credentials"); + touched = true; + break; + default: + user.resetCount(); + remove(user); + ++count; + removed = true; + break; + } + } + } + + // access.log(Level.AUDIT, "User Perm Expires", new Date(user.permExpires)); + if(!removed && lur!=null && user.permExpires<= now ) { + // access.log(Level.AUDIT, "Reloading"); + if(lur.reload(user).equals(Resp.REVALIDATED)) { + user.renewPerm(); + access.log(Level.DEBUG, "Reloaded Perms for",user); + touched = true; + } + } + user.resetCount(); + if(touched) { + ++renewed; + } + + } else { + if(user.permExpired()) { + remove(user); + ++count; + } + } + } + + // Clean out Misses + int missTotal = missMap.keySet().size(); + int miss = 0; + if(missTotal>0) { + ArrayList keys = new ArrayList(missTotal); + keys.addAll(missMap.keySet()); + for(String key : keys) { + Miss m = missMap.get(key); + if(m!=null && m.timestamp0) { + access.log(Level.INFO, (lur==null?"Cache":lur.getClass().getSimpleName()), "removed",count, + "and renewed",renewed,"expired Permissions out of", total,"and removed", miss, "password misses out of",missTotal); + } + + // If High (total) is reached during this period, increase the number of expired services removed for next time. + // There's no point doing it again here, as there should have been cleaned items. + if(total>high) { + // advance cleanup by 10%, without getting greater than timeInterval. + advance = Math.min(timeInterval, advance+(timeInterval/10)); + } else { + // reduce advance by 10%, without getting lower than 0. + advance = Math.max(0, advance-(timeInterval/10)); + } + } catch (Exception e) { + access.log(Level.ERROR,e.getMessage()); + } + } + } + + public static class Miss { + private static final int MAX_TRIES = 3; + + long timestamp; + byte[][] array; + + private long timetolive; + + private int tries; + + public Miss(byte[] first, long timeInterval) { + array = new byte[MAX_TRIES][]; + array[0]=first; + timestamp = System.currentTimeMillis() + timeInterval; + this.timetolive = timeInterval; + tries = 1; + } + + public boolean mayContinue(byte[] bs) { + if(++tries > MAX_TRIES) return false; + for(byte[] a : array) { + if(a==null)return true; + if(equals(a,bs)) { + return false; + } + } + return true; + } + + public synchronized boolean add(byte[] bc) { + if(++tries>MAX_TRIES)return false; + timestamp = System.currentTimeMillis()+timetolive; + for(int i=0;i