*
*/
public abstract class AbsUserCache<PERM extends Permission> {
- // Need an obvious key for when there is no Authentication Cred
- private static final String NO_CRED = "NoCred";
- static final int MIN_INTERVAL = 1000*60; // Min 1 min
- static final int MAX_INTERVAL = 1000*60*60*4; // 4 hour max
- private static Timer timer;
- // Map of userName to User
- private final Map<String, User<PERM>> userMap;
- private static final Map<String, Miss> missMap = new TreeMap<String,Miss>();
- private final Symm missEncrypt;
-
- private Clean clean;
- protected Access access;
-
- protected AbsUserCache(Access access, long cleanInterval, int highCount, int usageCount) {
- this.access = access;
- Symm s;
- try {
- byte[] gennedKey = Symm.keygen();
- s = Symm.obtain(new ByteArrayInputStream(gennedKey));
- } catch (IOException e) {
- access.log(e);
- s = Symm.base64noSplit;
- }
- missEncrypt = s;
-
- userMap = new ConcurrentHashMap<String, User<PERM>>();
+ // Need an obvious key for when there is no Authentication Cred
+ private static final String NO_CRED = "NoCred";
+ static final int MIN_INTERVAL = 1000*60; // Min 1 min
+ static final int MAX_INTERVAL = 1000*60*60*4; // 4 hour max
+ private static Timer timer;
+ // Map of userName to User
+ private final Map<String, User<PERM>> userMap;
+ private static final Map<String, Miss> missMap = new TreeMap<>();
+ private final Symm missEncrypt;
+
+ private Clean clean;
+ protected Access access;
+
+ protected AbsUserCache(Access access, long cleanInterval, int highCount, int usageCount) {
+ this.access = access;
+ Symm s;
+ try {
+ byte[] gennedKey = Symm.keygen();
+ s = Symm.obtain(new ByteArrayInputStream(gennedKey));
+ } catch (IOException e) {
+ access.log(e);
+ s = Symm.base64noSplit;
+ }
+ missEncrypt = s;
+
+ userMap = new ConcurrentHashMap<>();
-
- 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<PERM> cache) {
- this.access = cache.access;
- userMap = cache.userMap;
- missEncrypt = cache.missEncrypt;
-
- synchronized(AbsUserCache.class) {
- if(cache.clean!=null && cache.clean.lur==null && this instanceof CachingLur) {
- cache.clean.lur=(CachingLur<PERM>)this;
- }
- }
- }
+
+ 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<PERM> cache) {
+ this.access = cache.access;
+ userMap = cache.userMap;
+ missEncrypt = cache.missEncrypt;
+
+ synchronized(AbsUserCache.class) {
+ if (cache.clean!=null && cache.clean.lur==null && this instanceof CachingLur) {
+ cache.clean.lur=(CachingLur<PERM>)this;
+ }
+ }
+ }
- protected void setLur(CachingLur<PERM> lur) {
- if(clean!=null)clean.lur = lur;
-
- }
-
- protected void addUser(User<PERM> user) {
- Principal p = user.principal;
- String key;
- try {
- if(p instanceof GetCred) {
- key = missKey(p.getName(), ((GetCred)p).getCred());
- } else {
- byte[] cred;
- if((cred=user.getCred())==null) {
- key = user.name + NO_CRED;
- } else {
- key = missKey(user.name,cred);
- }
- }
- } catch (IOException e) {
- access.log(e);
- return;
- }
- userMap.put(key, user);
- }
+ protected void setLur(CachingLur<PERM> lur) {
+ if (clean!=null)clean.lur = lur;
+
+ }
+
+ protected void addUser(User<PERM> user) {
+ Principal p = user.principal;
+ String key;
+ try {
+ if (p instanceof GetCred) {
+ key = missKey(p.getName(), ((GetCred)p).getCred());
+ } else {
+ byte[] cred;
+ if ((cred=user.getCred())==null) {
+ key = user.name + NO_CRED;
+ } else {
+ key = missKey(user.name,cred);
+ }
+ }
+ } catch (IOException e) {
+ access.log(e);
+ return;
+ }
+ userMap.put(key, user);
+ }
- // Useful for looking up by WebToken, etc.
- protected void addUser(String key, User<PERM> 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
- * @throws IOException
- */
- protected synchronized boolean addMiss(String key, byte[] bs) {
- String mkey;
- try {
- mkey = missKey(key,bs);
- } catch (IOException e) {
- access.log(e);
- return false;
- }
- Miss miss = missMap.get(mkey);
- if(miss==null) {
- missMap.put(mkey, new Miss(bs,clean==null?MIN_INTERVAL:clean.timeInterval,key));
- return true;
- }
- return miss.mayContinue();
- }
+ // Useful for looking up by WebToken, etc.
+ protected void addUser(String key, User<PERM> 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
+ * @throws IOException
+ */
+ protected synchronized boolean addMiss(String key, byte[] bs) {
+ String mkey;
+ try {
+ mkey = missKey(key,bs);
+ } catch (IOException e) {
+ access.log(e);
+ return false;
+ }
+ Miss miss = missMap.get(mkey);
+ if (miss==null) {
+ missMap.put(mkey, new Miss(bs,clean==null?MIN_INTERVAL:clean.timeInterval,key));
+ return true;
+ }
+ return miss.mayContinue();
+ }
- protected Miss missed(String key, byte[] bs) throws IOException {
- return missMap.get(missKey(key,bs));
- }
+ protected Miss missed(String key, byte[] bs) throws IOException {
+ return missMap.get(missKey(key,bs));
+ }
- protected User<PERM> getUser(Principal principal) {
- String key;
- if(principal instanceof GetCred) {
- GetCred gc = (GetCred)principal;
- try {
- key = missKey(principal.getName(), gc.getCred());
- } catch (IOException e) {
- access.log(e, "Error getting key from Principal");
- key = principal.getName();
- }
- } else {
- key = principal.getName()+NO_CRED;
- }
- User<PERM> u = userMap.get(key);
- if(u!=null) {
- u.incCount();
- }
- return u;
- }
-
- protected User<PERM> getUser(CachedBasicPrincipal cbp) {
- return getUser(cbp.getName(), cbp.getCred());
- }
-
- protected User<PERM> getUser(String user, byte[] cred) {
- User<PERM> u;
- String key=null;
- try {
- key =missKey(user,cred);
- } catch (IOException e) {
- access.log(e);
- return null;
- }
- u = userMap.get(key);
- if(u!=null) {
- if(u.permExpired()) {
- userMap.remove(key);
- u=null;
- } else {
- u.incCount();
- }
- }
- return u;
- }
-
- /**
- * Removes User from the Cache
- * @param user
- */
- protected void remove(User<PERM> 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> dumpInfo() {
- List<DumpInfo> rv = new ArrayList<DumpInfo>();
- for(User<PERM> user : userMap.values()) {
- rv.add(new DumpInfo(user));
- }
- return rv;
- }
+ protected User<PERM> getUser(Principal principal) {
+ String key;
+ if (principal instanceof GetCred) {
+ GetCred gc = (GetCred)principal;
+ try {
+ key = missKey(principal.getName(), gc.getCred());
+ } catch (IOException e) {
+ access.log(e, "Error getting key from Principal");
+ key = principal.getName();
+ }
+ } else {
+ key = principal.getName()+NO_CRED;
+ }
+ User<PERM> u = userMap.get(key);
+ if (u!=null) {
+ u.incCount();
+ }
+ return u;
+ }
+
+ protected User<PERM> getUser(CachedBasicPrincipal cbp) {
+ return getUser(cbp.getName(), cbp.getCred());
+ }
+
+ protected User<PERM> getUser(String user, byte[] cred) {
+ User<PERM> u;
+ String key=null;
+ try {
+ key =missKey(user,cred);
+ } catch (IOException e) {
+ access.log(e);
+ return null;
+ }
+ u = userMap.get(key);
+ if (u!=null) {
+ if (u.permExpired()) {
+ userMap.remove(key);
+ u=null;
+ } else {
+ u.incCount();
+ }
+ }
+ return u;
+ }
+
+ /**
+ * Removes User from the Cache
+ * @param user
+ */
+ protected void remove(User<PERM> 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> dumpInfo() {
+ List<DumpInfo> rv = new ArrayList<>();
+ for (User<PERM> 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();
- }
- }
-
-
+ /**
+ * 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<String, Set<String>> groupMap = new HashMap<String, Set<String>>();
+ // Simple map of Group name to a set of User Names
+ // private Map<String, Set<String>> 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<String> perms;
-
- public DumpInfo(User<PERM> user) {
- this.user = user.principal.getName();
- perms = new ArrayList<String>(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.
- *
- * @author Jonathan
- *
- */
- private final class Clean extends TimerTask {
- private final Access access;
- private CachingLur<PERM> 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<User<PERM>> al = new ArrayList<User<PERM>>(userMap.values().size());
- al.addAll(0, userMap.values());
- long now = System.currentTimeMillis() + advance;
- for(User<PERM> user : al) {
- ++total;
- if(user.count>usageTriggerCount) {
- boolean touched = false, removed=false;
- if(user.principal instanceof CachedPrincipal) {
- CachedPrincipal cp = (CachedPrincipal)user.principal;
- if(cp.expires() < now) {
- switch(cp.revalidate(null)) {
- case INACCESSIBLE:
- access.log(Level.AUDIT, "AAF Inaccessible. Keeping credentials");
- break;
- case REVALIDATED:
- user.resetCount();
- touched = true;
- break;
- default:
- user.resetCount();
- remove(user);
- ++count;
- removed = true;
- break;
- }
- }
- }
-
- if(!removed && lur!=null && user.permExpires<= now ) {
- 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<String> keys = new ArrayList<String>(missTotal);
- keys.addAll(missMap.keySet());
- for(String key : keys) {
- Miss m = missMap.get(key);
- if(m!=null) {
- long timeLeft = m.timestamp - System.currentTimeMillis();
- if(timeLeft<0) {
- synchronized(missMap) {
- missMap.remove(key);
- }
- access.log(Level.INFO, m.name, " has been removed from Missed Credential Map (" + m.tries + " invalid tries)");
- ++miss;
- } else {
- access.log(Level.INFO, m.name, " remains in Missed Credential Map (" + m.tries + " invalid tries) for " + (timeLeft/1000) + " more seconds");
- }
- }
- }
- }
-
- if(count+renewed+miss>0) {
- 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());
- }
- }
- }
+ /**
+ * 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<String> perms;
+
+ public DumpInfo(User<PERM> 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.
+ *
+ * @author Jonathan
+ *
+ */
+ private final class Clean extends TimerTask {
+ private final Access access;
+ private CachingLur<PERM> 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<User<PERM>> al = new ArrayList<>(userMap.values().size());
+ al.addAll(0, userMap.values());
+ long now = System.currentTimeMillis() + advance;
+ for (User<PERM> user : al) {
+ ++total;
+ if (user.count>usageTriggerCount) {
+ boolean touched = false, removed=false;
+ if (user.principal instanceof CachedPrincipal) {
+ CachedPrincipal cp = (CachedPrincipal)user.principal;
+ if (cp.expires() < now) {
+ switch(cp.revalidate(null)) {
+ case INACCESSIBLE:
+ access.log(Level.AUDIT, "AAF Inaccessible. Keeping credentials");
+ break;
+ case REVALIDATED:
+ user.resetCount();
+ touched = true;
+ break;
+ default:
+ user.resetCount();
+ remove(user);
+ ++count;
+ removed = true;
+ break;
+ }
+ }
+ }
+
+ if (!removed && lur!=null && user.permExpires<= now ) {
+ 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<String> keys = new ArrayList<>(missTotal);
+ keys.addAll(missMap.keySet());
+ for (String key : keys) {
+ Miss m = missMap.get(key);
+ if (m!=null) {
+ long timeLeft = m.timestamp - System.currentTimeMillis();
+ if (timeLeft<0) {
+ synchronized(missMap) {
+ missMap.remove(key);
+ }
+ access.log(Level.INFO, m.name, " has been removed from Missed Credential Map (" + m.tries + " invalid tries)");
+ ++miss;
+ } else {
+ access.log(Level.INFO, m.name, " remains in Missed Credential Map (" + m.tries + " invalid tries) for " + (timeLeft/1000) + " more seconds");
+ }
+ }
+ }
+ }
+
+ if (count+renewed+miss>0) {
+ 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());
+ }
+ }
+ }
- private String missKey(String name, byte[] bs) throws IOException {
- return name + Hash.toHex(missEncrypt.encode(bs));
- }
+ private String missKey(String name, byte[] bs) throws IOException {
+ return name + Hash.toHex(missEncrypt.encode(bs));
+ }
- protected static class Miss {
- private static final int MAX_TRIES = 3;
+ protected static class Miss {
+ private static final int MAX_TRIES = 3;
- long timestamp;
+ long timestamp;
- private long timetolive;
+ private long timetolive;
- private long tries;
+ private long tries;
- private final String name;
-
- public Miss(final byte[] first, final long timeInterval, final String name) {
- timestamp = System.currentTimeMillis() + timeInterval;
- this.timetolive = timeInterval;
- tries = 0L;
- this.name = name;
- }
-
-
- public synchronized boolean mayContinue() {
- long ts = System.currentTimeMillis();
- if(ts>timestamp) {
- tries = 0;
- timestamp = ts + timetolive;
- } else if(MAX_TRIES <= ++tries) {
- return false;
- }
- return true;
- }
-
- }
-
- /**
- * Report on state
- */
- public String toString() {
- return getClass().getSimpleName() +
- " Cache:\n Users Cached: " +
- userMap.size() +
- "\n Misses Saved: " +
- missMap.size() +
- '\n';
-
- }
+ private final String name;
+
+ public Miss(final byte[] first, final long timeInterval, final String name) {
+ timestamp = System.currentTimeMillis() + timeInterval;
+ this.timetolive = timeInterval;
+ tries = 0L;
+ this.name = name;
+ }
+
+
+ public synchronized boolean mayContinue() {
+ long ts = System.currentTimeMillis();
+ if (ts>timestamp) {
+ tries = 0;
+ timestamp = ts + timetolive;
+ } else if (MAX_TRIES <= ++tries) {
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+ /**
+ * Report on state
+ */
+ public String toString() {
+ return getClass().getSimpleName() +
+ " Cache:\n Users Cached: " +
+ userMap.size() +
+ "\n Misses Saved: " +
+ missMap.size() +
+ '\n';
+
+ }
- public void clear(Principal p, StringBuilder sb) {
- sb.append(toString());
- userMap.clear();
- missMap.clear();
- access.log(Level.AUDIT, p.getName(),"has cleared User Cache in",getClass().getSimpleName());
- sb.append("Now cleared\n");
- }
+ public void clear(Principal p, StringBuilder sb) {
+ sb.append(toString());
+ userMap.clear();
+ missMap.clear();
+ access.log(Level.AUDIT, p.getName(),"has cleared User Cache in",getClass().getSimpleName());
+ sb.append("Now cleared\n");
+ }
}
\ No newline at end of file