1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 module hunt.shiro.realm.CachingRealm; 20 21 import hunt.shiro.realm.Realm; 22 23 import hunt.shiro.authc.LogoutAware; 24 import hunt.shiro.cache.CacheManager; 25 import hunt.shiro.cache.CacheManagerAware; 26 import hunt.shiro.subject.PrincipalCollection; 27 import hunt.shiro.util.CollectionUtils; 28 import hunt.shiro.util.Common; 29 import hunt.logging.Logger; 30 31 import hunt.collection; 32 33 import core.atomic; 34 import std.array; 35 import std.conv; 36 37 /** 38 * A very basic abstract extension point for the {@link Realm} interface that provides caching support for subclasses. 39 * <p/> 40 * It also provides a convenience method, 41 * {@link #getAvailablePrincipal(hunt.shiro.subject.PrincipalCollection)}, which is useful across all 42 * realm subclasses for obtaining a realm-specific principal/identity. 43 * <p/> 44 * All actual Realm method implementations are left to subclasses. 45 * 46 * @see #clearCache(hunt.shiro.subject.PrincipalCollection) 47 * @see #onLogout(hunt.shiro.subject.PrincipalCollection) 48 * @see #getAvailablePrincipal(hunt.shiro.subject.PrincipalCollection) 49 */ 50 abstract class CachingRealm : Realm, Nameable, CacheManagerAware, LogoutAware { 51 52 private static shared int INSTANCE_COUNT = 0; // 53 54 /*-------------------------------------------- 55 | I N S T A N C E V A R I A B L E S | 56 ============================================*/ 57 private string name; 58 private bool cachingEnabled; 59 private CacheManager cacheManager; 60 61 /** 62 * Default no-argument constructor that defaults 63 * {@link #isCachingEnabled() cachingEnabled} (for general caching) to {@code true} and sets a 64 * default {@link #getName() name} based on the class name. 65 * <p/> 66 * Note that while in general, caching may be enabled by default, subclasses have control over 67 * if specific caching is enabled. 68 */ 69 this() { 70 this.cachingEnabled = true; 71 this.name = typeid(this).name ~ "_" ~ INSTANCE_COUNT.to!string(); 72 atomicOp!("+=")(INSTANCE_COUNT, 1); 73 } 74 75 /** 76 * Returns the <tt>CacheManager</tt> used for data caching to reduce EIS round trips, or <tt>null</tt> if 77 * caching is disabled. 78 * 79 * @return the <tt>CacheManager</tt> used for data caching to reduce EIS round trips, or <tt>null</tt> if 80 * caching is disabled. 81 */ 82 CacheManager getCacheManager() { 83 return this.cacheManager; 84 } 85 86 /** 87 * Sets the <tt>CacheManager</tt> to be used for data caching to reduce EIS round trips. 88 * <p/> 89 * This property is <tt>null</tt> by default, indicating that caching is turned off. 90 * 91 * @param cacheManager the <tt>CacheManager</tt> to use for data caching, or <tt>null</tt> to disable caching. 92 */ 93 void setCacheManager(CacheManager cacheManager) { 94 this.cacheManager = cacheManager; 95 afterCacheManagerSet(); 96 } 97 98 /** 99 * Returns {@code true} if caching should be used if a {@link CacheManager} has been 100 * {@link #setCacheManager(hunt.shiro.cache.CacheManager) configured}, {@code false} otherwise. 101 * <p/> 102 * The default value is {@code true} since the large majority of Realms will benefit from caching if a CacheManager 103 * has been configured. However, memory-only realms should set this value to {@code false} since they would 104 * manage account data in memory already lookups would already be as efficient as possible. 105 * 106 * @return {@code true} if caching will be globally enabled if a {@link CacheManager} has been 107 * configured, {@code false} otherwise 108 */ 109 bool isCachingEnabled() { 110 return cachingEnabled; 111 } 112 113 /** 114 * Sets whether or not caching should be used if a {@link CacheManager} has been 115 * {@link #setCacheManager(hunt.shiro.cache.CacheManager) configured}. 116 * 117 * @param cachingEnabled whether or not to globally enable caching for this realm. 118 */ 119 void setCachingEnabled(bool cachingEnabled) { 120 this.cachingEnabled = cachingEnabled; 121 } 122 123 string getName() { 124 return name; 125 } 126 127 void setName(string name) { 128 this.name = name; 129 } 130 131 /** 132 * Template method that may be implemented by subclasses should they wish to react to a 133 * {@link CacheManager} instance being set on the realm instance via the 134 * {@link #setCacheManager(hunt.shiro.cache.CacheManager)} mutator. 135 */ 136 protected void afterCacheManagerSet() { 137 } 138 139 /** 140 * If caching is enabled, this will clear any cached data associated with the specified account identity. 141 * Subclasses are free to override for additional behavior, but be sure to call {@code super.onLogout} first. 142 * <p/> 143 * This default implementation merely calls {@link #clearCache(hunt.shiro.subject.PrincipalCollection)}. 144 * 145 * @param principals the application-specific Subject/user identifier that is logging out. 146 * @see #clearCache(hunt.shiro.subject.PrincipalCollection) 147 * @see #getAvailablePrincipal(hunt.shiro.subject.PrincipalCollection) 148 */ 149 void onLogout(PrincipalCollection principals) { 150 clearCache(principals); 151 } 152 153 private static bool isEmpty(PrincipalCollection pc) { 154 return pc is null || pc.isEmpty(); 155 } 156 157 /** 158 * Clears out any cached data associated with the specified account identity/identities. 159 * <p/> 160 * This implementation will return quietly if the principals argument is null or empty. Otherwise it delegates 161 * to {@link #doClearCache(hunt.shiro.subject.PrincipalCollection)}. 162 * 163 * @param principals the principals of the account for which to clear any cached data. 164 */ 165 protected void clearCache(PrincipalCollection principals) { 166 if (!isEmpty(principals)) { 167 doClearCache(principals); 168 version(HUNT_SHIRO_DEBUG) tracef("Cleared cache entries for account with principals [%s]", principals); 169 } 170 } 171 172 /** 173 * This implementation does nothing - it is a template to be overridden by subclasses if necessary. 174 * 175 * @param principals principals the principals of the account for which to clear any cached data. 176 */ 177 protected void doClearCache(PrincipalCollection principals) { 178 } 179 180 /** 181 * A utility method for subclasses that returns the first available principal of interest to this particular realm. 182 * The heuristic used to acquire the principal is as follows: 183 * <ul> 184 * <li>Attempt to get <em>this particular Realm's</em> 'primary' principal in the {@code PrincipalCollection} via a 185 * <code>principals.{@link PrincipalCollection#fromRealm(string) fromRealm}({@link #getName() getName()})</code> 186 * call.</li> 187 * <li>If the previous call does not result in any principals, attempt to get the overall 'primary' principal 188 * from the PrincipalCollection via {@link hunt.shiro.subject.PrincipalCollection#getPrimaryPrincipal()}.</li> 189 * <li>If there are no principals from that call (or the PrincipalCollection argument was null to begin with), 190 * return {@code null}</li> 191 * </ul> 192 * 193 * @param principals the PrincipalCollection holding all principals (from all realms) associated with a single Subject. 194 * @return the 'primary' principal attributed to this particular realm, or the fallback 'master' principal if it 195 * exists, or if not {@code null}. 196 */ 197 protected Object getAvailablePrincipal(PrincipalCollection principals) { 198 Object primary = null; 199 if (!isEmpty(principals)) { 200 Object[] thisPrincipals = principals.fromRealm(getName()); 201 if (!thisPrincipals.empty()) { 202 primary = thisPrincipals[0]; 203 } else { 204 //no principals attributed to this particular realm. Fall back to the 'master' primary: 205 primary = principals.getPrimaryPrincipal(); 206 } 207 } 208 209 return primary; 210 } 211 }