001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.monitor; 029 030import org.opencms.file.CmsGroup; 031import org.opencms.file.CmsUser; 032import org.opencms.main.CmsLog; 033import org.opencms.security.CmsRole; 034import org.opencms.util.CmsUUID; 035 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.List; 039import java.util.Map; 040import java.util.concurrent.ConcurrentHashMap; 041 042import org.apache.commons.logging.Log; 043 044import com.google.common.cache.CacheBuilder; 045import com.google.common.cache.CacheLoader; 046import com.google.common.cache.LoadingCache; 047import com.google.common.collect.Interner; 048import com.google.common.collect.Interners; 049 050/** 051 * Cache for users' groups and data derived from those groups, like role membership. 052 * 053 * <p>The cache can be either flushed completely, or just for a single user id. 054 * The data for a user must be flushed when their group membership changes. 055 * 056 */ 057public class CmsGroupListCache implements I_CmsMemoryMonitorable { 058 059 /** 060 * Internal entry which stores the cached data for a specific user id.<p> 061 */ 062 class Entry implements I_CmsMemoryMonitorable { 063 064 /** Bare roles, with no OU information. */ 065 private volatile List<CmsRole> m_bareRoles; 066 067 /** Cache for group lists. */ 068 private Map<String, List<CmsGroup>> m_groupCache = createLRUCacheMap(GROUP_LISTS_PER_USER); 069 070 /** Cache for role memberships. */ 071 private Map<String, Boolean> m_hasRoleCache = new ConcurrentHashMap<>(); 072 073 /** 074 * Gets the cached bare roles (with no OU information). 075 * 076 * @return the cached bare roles 077 */ 078 public List<CmsRole> getBareRoles() { 079 080 return m_bareRoles; 081 } 082 083 /** 084 * Gets the group list cache map. 085 * 086 * @return the group list cache 087 */ 088 public Map<String, List<CmsGroup>> getGroupCache() { 089 090 return m_groupCache; 091 } 092 093 /** 094 * Gets the 'hasRole' cache map. 095 * 096 * @return the 'hasRole' cache 097 */ 098 public Map<String, Boolean> getHasRoleCache() { 099 100 return m_hasRoleCache; 101 } 102 103 /** 104 * @see org.opencms.monitor.I_CmsMemoryMonitorable#getMemorySize() 105 */ 106 public int getMemorySize() { 107 108 return (int)(CmsMemoryMonitor.getValueSize(m_groupCache) 109 + CmsMemoryMonitor.getValueSize(m_hasRoleCache) 110 + CmsMemoryMonitor.getValueSize(m_bareRoles)); 111 } 112 113 /** 114 * Sets the cached bare roles (with no associated OU information). 115 * 116 * @param bareRoles the bare roles 117 */ 118 public void setBareRoles(List<CmsRole> bareRoles) { 119 120 m_bareRoles = bareRoles; 121 } 122 123 } 124 125 /** Max cached group lists per user. Non-final, so can be adjusted at runtime. */ 126 public static volatile int GROUP_LISTS_PER_USER = 32; 127 128 /** Log instance for this class. */ 129 private static final Log LOG = CmsLog.getLog(CmsGroupListCache.class); 130 131 /** The internal cache used. */ 132 private LoadingCache<CmsUUID, Entry> m_internalCache; 133 134 /** Interner for canonicalizing the role membership cache keys. */ 135 private Interner<String> m_interner = Interners.newBuilder().concurrencyLevel(8).build(); 136 137 /** 138 * Creates a new cache instance. 139 * 140 * @param size the maximum size 141 */ 142 public CmsGroupListCache(int size) { 143 144 m_internalCache = CacheBuilder.newBuilder().concurrencyLevel(CmsMemoryMonitor.CONCURRENCY_LEVEL).maximumSize( 145 size).build(new CacheLoader<CmsUUID, Entry>() { 146 147 @Override 148 public Entry load(CmsUUID key) throws Exception { 149 150 return new Entry(); 151 } 152 }); 153 } 154 155 /** 156 * Creates a thread safe LRU cache map based on the guava cache builder.<p> 157 * 158 * @param capacity the cache capacity 159 * 160 * @return the cache map 161 */ 162 @SuppressWarnings("unchecked") 163 static <T, V> Map<T, V> createLRUCacheMap(int capacity) { 164 165 CacheBuilder<?, ?> builder = CacheBuilder.newBuilder().concurrencyLevel(4).maximumSize(capacity); 166 return (Map<T, V>)(builder.build().asMap()); 167 } 168 169 /** 170 * Removes all cache entries. 171 */ 172 public void clear() { 173 174 if (LOG.isInfoEnabled()) { 175 if (LOG.isDebugEnabled()) { 176 // when DEBUG level is enabled, log a dummy exception to generate a stack trace 177 LOG.debug("CmsGroupListCache.clear() called", new Exception("(dummy exception)")); 178 } else { 179 LOG.info("CmsGroupListCache.clear() called"); 180 } 181 } 182 m_internalCache.invalidateAll(); 183 } 184 185 /** 186 * Removes the cache entries for the given user id. 187 * 188 * @param idKey the user id 189 */ 190 public void clearUser(CmsUUID idKey) { 191 192 if (idKey != null) { 193 m_internalCache.invalidate(idKey); 194 } 195 } 196 197 /** 198 * Gets the cached bare roles for the given user id, or null if none are cached. 199 * 200 * <p>These are just the roles of the user, but with no OU information. 201 202 * @param userId the user id 203 * @return the bare roles for the user 204 */ 205 public List<CmsRole> getBareRoles(CmsUUID userId) { 206 207 Entry entry = m_internalCache.getIfPresent(userId); 208 if (entry == null) { 209 return null; 210 } 211 return entry.getBareRoles(); 212 } 213 214 /** 215 * Gets the cached user groups for the given combination of keys, or null if nothing is cached. 216 * 217 * @param userId the user id 218 * @param subKey a string that consists of the parameters/flags for the group reading operation 219 * 220 * @return the groups for the given combination of keys 221 */ 222 public List<CmsGroup> getGroups(CmsUUID userId, String subKey) { 223 224 Entry userEntry = m_internalCache.getIfPresent(userId); 225 if (userEntry == null) { 226 return null; 227 } 228 List<CmsGroup> result = userEntry.getGroupCache().get(subKey); 229 if (result != null) { 230 result = Collections.unmodifiableList(result); 231 } 232 return result; 233 234 } 235 236 /** 237 * Gets the cached role membership for the given role key, or null if nothing is cached. 238 * 239 * @param userId the user id 240 * @param roleKey the role key 241 * 242 * @return the cached role membership 243 */ 244 public Boolean getHasRole(CmsUUID userId, String roleKey) { 245 246 Entry userEntry = m_internalCache.getIfPresent(userId); 247 if (userEntry == null) { 248 return null; 249 } 250 return userEntry.getHasRoleCache().get(roleKey); 251 } 252 253 /** 254 * @see org.opencms.monitor.I_CmsMemoryMonitorable#getMemorySize() 255 */ 256 public int getMemorySize() { 257 258 return (int)CmsMemoryMonitor.getValueSize(m_internalCache.asMap()); 259 } 260 261 /** 262 * Sets the bare roles for a user (with no OU information). 263 * 264 * @param user the user 265 * @param bareRoles the list of bare roles 266 */ 267 public void setBareRoles(CmsUser user, List<CmsRole> bareRoles) { 268 269 if (!user.isWebuser()) { // web users take away space from normal workplace users/editors 270 CmsUUID userId = user.getId(); 271 m_internalCache.getUnchecked(userId).setBareRoles(bareRoles); 272 } 273 } 274 275 /** 276 * Caches a new value for the given combination of keys. 277 * 278 * @param user the user 279 * @param subKey a string that consists of the parameters/flags for the group reading operation 280 * @param groups the value to cache 281 */ 282 public void setGroups(CmsUser user, String subKey, List<CmsGroup> groups) { 283 284 if (!user.isWebuser()) { // web users take away space from normal workplace users/editors 285 CmsUUID userId = user.getId(); 286 Map<String, List<CmsGroup>> groupCache = m_internalCache.getUnchecked(userId).getGroupCache(); 287 groupCache.put(subKey, new ArrayList<>(groups)); 288 } 289 } 290 291 /** 292 * Caches the role membership for the given user id and role key. 293 * 294 * @param user the user 295 * @param roleKey the role key 296 * @param value the role membership value 297 */ 298 public void setHasRole(CmsUser user, String roleKey, Boolean value) { 299 300 if (!user.isWebuser()) { // web users take away space from normal workplace users/editors 301 roleKey = m_interner.intern(roleKey); 302 CmsUUID userId = user.getId(); 303 m_internalCache.getUnchecked(userId).getHasRoleCache().put(roleKey, value); 304 } 305 } 306 307 /** 308 * Returns the number of user ids for which group lists are cached. 309 * 310 * @return the number of user ids for which group lists are cached 311 */ 312 public int size() { 313 314 return (int)m_internalCache.size(); 315 } 316 317}