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.subject.SimplePrincipalCollection; 20 21 import hunt.shiro.subject.PrincipalCollection; 22 23 import hunt.shiro.util.CollectionUtils; 24 // import hunt.shiro.util.StringUtils; 25 import hunt.shiro.subject.MutablePrincipalCollection; 26 27 import hunt.collection; 28 import hunt.logging.Logger; 29 import hunt.Exceptions; 30 import hunt.String; 31 import hunt.text.StringUtils; 32 33 import std.array; 34 import std.range; 35 // import java.io.IOException; 36 // import java.io.ObjectInputStream; 37 // import java.io.ObjectOutputStream; 38 39 40 /** 41 * A simple implementation of the {@link MutablePrincipalCollection} interface that tracks principals internally 42 * by storing them in a {@link LinkedHashMap}. 43 * 44 */ 45 class SimplePrincipalCollection : MutablePrincipalCollection { 46 47 // Serialization reminder: 48 // You _MUST_ change this number if you introduce a change to this class 49 // that is NOT serialization backwards compatible. Serialization-compatible 50 // changes do not require a change to this number. If you need to generate 51 // a new number in this case, use the JDK's 'serialver' program to generate it. 52 53 54 private Map!(string, Set!Object) realmPrincipals; 55 56 private string cachedToString; //cached toString() result, as this can be printed many times in logging 57 58 this() { 59 } 60 61 this(string principal, string realmName) { 62 this(new String(principal), realmName); 63 } 64 65 this(Object principal, string realmName) { 66 Collection!Object c = cast(Collection!Object) principal; 67 if (c !is null) { 68 addAll(c, realmName); 69 } else { 70 add(principal, realmName); 71 } 72 } 73 74 this(Collection!Object principals, string realmName) { 75 addAll(principals, realmName); 76 } 77 78 this(PrincipalCollection principals) { 79 addAll(principals); 80 } 81 82 protected Collection!Object getPrincipalsLazy(string realmName) { 83 if (realmPrincipals is null) { 84 realmPrincipals = new LinkedHashMap!(string, Set!Object)(); 85 } 86 87 Set!Object principals = null; 88 try { 89 if(realmPrincipals.containsKey(realmName)) { 90 principals = realmPrincipals.get(realmName); 91 } 92 } catch(Exception ex) { 93 version(HUNT_DEBUG) warning(ex.msg); 94 } 95 96 if (principals is null) { 97 principals = new LinkedHashSet!Object(); 98 realmPrincipals.put(realmName, principals); 99 } 100 return principals; 101 } 102 103 /** 104 * Returns the first available principal from any of the {@code Realm} principals, or {@code null} if there are 105 * no principals yet. 106 * <p/> 107 * The 'first available principal' is interpreted as the principal that would be returned by 108 * <code>{@link #iterator() iterator()}.{@link java.util.Iterator#next() next()}.</code> 109 * 110 * @inheritDoc 111 */ 112 Object getPrimaryPrincipal() { 113 if (isEmpty()) { 114 return null; 115 } 116 return asSet().toArray()[0]; 117 } 118 119 void add(string principal, string realmName) { 120 add(new String(principal), realmName); 121 } 122 123 void add(Object principal, string realmName) { 124 if (realmName is null) { 125 throw new IllegalArgumentException("realmName argument cannot be null."); 126 } 127 if (principal is null) { 128 throw new IllegalArgumentException("principal argument cannot be null."); 129 } 130 this.cachedToString = null; 131 getPrincipalsLazy(realmName).add(principal); 132 } 133 134 void addAll(Collection!Object principals, string realmName) { 135 if (realmName is null) { 136 throw new IllegalArgumentException("realmName argument cannot be null."); 137 } 138 if (principals is null) { 139 throw new IllegalArgumentException("principals argument cannot be null."); 140 } 141 if (principals.isEmpty()) { 142 throw new IllegalArgumentException("principals argument cannot be an empty collection."); 143 } 144 this.cachedToString = null; 145 getPrincipalsLazy(realmName).addAll(principals); 146 } 147 148 void addAll(PrincipalCollection principals) { 149 if (principals.getRealmNames() !is null) { 150 foreach(string realmName ; principals.getRealmNames()) { 151 foreach(Object principal ; principals.fromRealm(realmName)) { 152 add(principal, realmName); 153 } 154 } 155 } 156 } 157 158 T oneByType(T)() if(is(T == class) || is(T == interface)) { 159 if (realmPrincipals is null || realmPrincipals.isEmpty()) { 160 return null; 161 } 162 Set!(Object)[] values = realmPrincipals.values(); 163 foreach(Set!Object set ; values) { 164 foreach(Object o ; set) { 165 T v = cast(T)o; 166 if(o !is null && v !is null) 167 return v; 168 } 169 } 170 return null; 171 } 172 173 Collection!(T) byType(T)() if(is(T == class) || is(T == interface)) { 174 if (realmPrincipals is null || realmPrincipals.isEmpty()) { 175 return Collections.emptySet!T; 176 } 177 Set!(T) typed = new LinkedHashSet!(T)(); 178 Set!(Object)[] values = realmPrincipals.values(); 179 foreach(Set set ; values) { 180 foreach(Object o ; set) { 181 T v = cast(T)o; 182 if(o !is null && v !is null) { 183 typed.add(v); 184 } 185 } 186 } 187 if (typed.isEmpty()) { 188 return Collections.emptySet!T; 189 } 190 return typed; 191 } 192 193 List!Object asList() { 194 Set!Object all = asSet(); 195 if (all.isEmpty()) { 196 return Collections.emptyList!Object(); 197 } 198 return new ArrayList!Object(all); 199 } 200 201 Set!Object asSet() { 202 if (realmPrincipals is null || realmPrincipals.isEmpty()) { 203 return Collections.emptySet!(Object)(); 204 } 205 206 Set!Object aggregated = new LinkedHashSet!Object(); 207 foreach(Set!(Object) set ; realmPrincipals.byValue()) { 208 aggregated.addAll(set); 209 } 210 if (aggregated.isEmpty()) { 211 return Collections.emptySet!(Object)(); 212 } 213 return aggregated; 214 } 215 216 Object[] fromRealm(string realmName) { 217 if (realmPrincipals is null || realmPrincipals.isEmpty()) { 218 return null; 219 } 220 Set!Object principals = realmPrincipals.get(realmName); 221 if (principals is null || principals.isEmpty()) { 222 return null; 223 } else { 224 return principals.toArray(); 225 } 226 } 227 228 string[] getRealmNames() { 229 if (realmPrincipals is null) { 230 return null; 231 } else { 232 return realmPrincipals.byKey.array(); 233 } 234 } 235 236 bool isEmpty() { 237 return realmPrincipals is null || realmPrincipals.isEmpty(); 238 } 239 240 void clear() { 241 this.cachedToString = null; 242 if (realmPrincipals !is null) { 243 realmPrincipals.clear(); 244 realmPrincipals = null; 245 } 246 } 247 248 // Iterator iterator() { 249 // return asSet().iterator(); 250 // } 251 252 override bool opEquals(Object o) { 253 if (o is this) { 254 return true; 255 } 256 257 SimplePrincipalCollection other = cast(SimplePrincipalCollection) o; 258 if (other !is null) { 259 return this.realmPrincipals !is null ? 260 this.realmPrincipals == other.realmPrincipals : 261 other.realmPrincipals is null; 262 } 263 return false; 264 } 265 266 override size_t toHash() @trusted nothrow { 267 try { 268 if (this.realmPrincipals !is null && !realmPrincipals.isEmpty()) { 269 return realmPrincipals.toHash(); 270 } 271 } catch(Exception ex) { 272 warning(ex.msg); 273 } 274 return super.toHash(); 275 } 276 277 /** 278 * Returns a simple string representation suitable for printing. 279 * 280 * @return a simple string representation suitable for printing. 281 */ 282 override string toString() { 283 if (this.cachedToString is null) { 284 Set!(Object) principals = asSet(); 285 if (!CollectionUtils.isEmpty(principals)) { 286 this.cachedToString = StringUtils.toCommaDelimitedString(principals.toArray()); 287 } else { 288 this.cachedToString = "empty"; 289 } 290 } 291 return this.cachedToString; 292 } 293 294 295 /** 296 * Serialization write support. 297 * <p/> 298 * NOTE: Don't forget to change the serialVersionUID constant at the top of this class 299 * if you make any backwards-incompatible serialization changes!!! 300 * (use the JDK 'serialver' program for this) 301 * 302 * @param out output stream provided by Java serialization 303 * @throws IOException if there is a stream error 304 */ 305 // private void writeObject(ObjectOutputStream out){ 306 // out.defaultWriteObject(); 307 // bool principalsExist = !CollectionUtils.isEmpty(realmPrincipals); 308 // out.writebool(principalsExist); 309 // if (principalsExist) { 310 // out.writeObject(realmPrincipals); 311 // } 312 // } 313 314 /** 315 * Serialization read support - reads in the Map principals collection if it exists in the 316 * input stream. 317 * <p/> 318 * NOTE: Don't forget to change the serialVersionUID constant at the top of this class 319 * if you make any backwards-incompatible serialization changes!!! 320 * (use the JDK 'serialver' program for this) 321 * 322 * @param in input stream provided by 323 * @throws IOException if there is an input/output problem 324 * @throws ClassNotFoundException if the underlying Map implementation class is not available to the classloader. 325 */ 326 // private void readObject(ObjectInputStream in){ 327 // in.defaultReadObject(); 328 // bool principalsExist = in.readbool(); 329 // if (principalsExist) { 330 // this.realmPrincipals = (Map!(string, Set)) in.readObject(); 331 // } 332 // } 333 334 335 int opApply(scope int delegate(ref Object) dg) { 336 337 int result = 0; 338 foreach(Object obj; asSet()) { 339 result = dg(obj); 340 } 341 return result; 342 } 343 }