001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.subject; 020 021import org.apache.shiro.util.CollectionUtils; 022import org.apache.shiro.lang.util.StringUtils; 023 024import java.io.IOException; 025import java.io.ObjectInputStream; 026import java.io.ObjectOutputStream; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.Iterator; 031import java.util.LinkedHashMap; 032import java.util.LinkedHashSet; 033import java.util.List; 034import java.util.Map; 035import java.util.Objects; 036import java.util.Set; 037 038/** 039 * A simple implementation of the {@link MutablePrincipalCollection} interface that tracks principals internally 040 * by storing them in a {@link LinkedHashMap}. 041 * 042 * @since 0.9 043 */ 044@SuppressWarnings({"unchecked"}) 045public class SimplePrincipalCollection implements MutablePrincipalCollection { 046 047 // Serialization reminder: 048 // You _MUST_ change this number if you introduce a change to this class 049 // that is NOT serialization backwards compatible. Serialization-compatible 050 // changes do not require a change to this number. If you need to generate 051 // a new number in this case, use the JDK's 'serialver' program to generate it. 052 private static final long serialVersionUID = -6305224034025797558L; 053 054 //TODO - complete JavaDoc 055 private Map<String, Set> realmPrincipals; 056 057 //cached toString() result, as this can be printed many times in logging 058 private transient String cachedToString; 059 060 public SimplePrincipalCollection() { 061 } 062 063 public SimplePrincipalCollection(Object principal, String realmName) { 064 if (principal instanceof Collection) { 065 addAll((Collection) principal, realmName); 066 } else { 067 add(principal, realmName); 068 } 069 } 070 071 public SimplePrincipalCollection(Collection principals, String realmName) { 072 addAll(principals, realmName); 073 } 074 075 public SimplePrincipalCollection(PrincipalCollection principals) { 076 addAll(principals); 077 } 078 079 protected Collection getPrincipalsLazy(String realmName) { 080 if (realmPrincipals == null) { 081 realmPrincipals = new LinkedHashMap<String, Set>(); 082 } 083 Set principals = realmPrincipals.get(realmName); 084 if (principals == null) { 085 principals = new LinkedHashSet(); 086 realmPrincipals.put(realmName, principals); 087 } 088 return principals; 089 } 090 091 /** 092 * Returns the first available principal from any of the {@code Realm} principals, or {@code null} if there are 093 * no principals yet. 094 * <p/> 095 * The 'first available principal' is interpreted as the principal that would be returned by 096 * <code>{@link #iterator() iterator()}.{@link java.util.Iterator#next() next()}.</code> 097 * 098 * @inheritDoc 099 */ 100 public Object getPrimaryPrincipal() { 101 if (isEmpty()) { 102 return null; 103 } 104 return iterator().next(); 105 } 106 107 public void add(Object principal, String realmName) { 108 if (realmName == null) { 109 throw new NullPointerException("realmName argument cannot be null."); 110 } 111 if (principal == null) { 112 throw new NullPointerException("principal argument cannot be null."); 113 } 114 this.cachedToString = null; 115 getPrincipalsLazy(realmName).add(principal); 116 } 117 118 public void addAll(Collection principals, String realmName) { 119 if (realmName == null) { 120 throw new NullPointerException("realmName argument cannot be null."); 121 } 122 if (principals == null) { 123 throw new NullPointerException("principals argument cannot be null."); 124 } 125 if (principals.isEmpty()) { 126 throw new IllegalArgumentException("principals argument cannot be an empty collection."); 127 } 128 this.cachedToString = null; 129 getPrincipalsLazy(realmName).addAll(principals); 130 } 131 132 public void addAll(PrincipalCollection principals) { 133 if (principals.getRealmNames() != null) { 134 for (String realmName : principals.getRealmNames()) { 135 for (Object principal : principals.fromRealm(realmName)) { 136 add(principal, realmName); 137 } 138 } 139 } 140 } 141 142 public <T> T oneByType(Class<T> type) { 143 if (realmPrincipals == null || realmPrincipals.isEmpty()) { 144 return null; 145 } 146 Collection<Set> values = realmPrincipals.values(); 147 for (Set set : values) { 148 for (Object o : set) { 149 if (type.isAssignableFrom(o.getClass())) { 150 return (T) o; 151 } 152 } 153 } 154 return null; 155 } 156 157 public <T> Collection<T> byType(Class<T> type) { 158 if (realmPrincipals == null || realmPrincipals.isEmpty()) { 159 return Collections.EMPTY_SET; 160 } 161 Set<T> typed = new LinkedHashSet<T>(); 162 Collection<Set> values = realmPrincipals.values(); 163 for (Set set : values) { 164 for (Object o : set) { 165 if (type.isAssignableFrom(o.getClass())) { 166 typed.add((T) o); 167 } 168 } 169 } 170 if (typed.isEmpty()) { 171 return Collections.EMPTY_SET; 172 } 173 return Collections.unmodifiableSet(typed); 174 } 175 176 public List asList() { 177 Set all = asSet(); 178 if (all.isEmpty()) { 179 return Collections.EMPTY_LIST; 180 } 181 return Collections.unmodifiableList(new ArrayList(all)); 182 } 183 184 public Set asSet() { 185 if (realmPrincipals == null || realmPrincipals.isEmpty()) { 186 return Collections.EMPTY_SET; 187 } 188 Set aggregated = new LinkedHashSet(); 189 Collection<Set> values = realmPrincipals.values(); 190 for (Set set : values) { 191 aggregated.addAll(set); 192 } 193 if (aggregated.isEmpty()) { 194 return Collections.EMPTY_SET; 195 } 196 return Collections.unmodifiableSet(aggregated); 197 } 198 199 public Collection fromRealm(String realmName) { 200 if (realmPrincipals == null || realmPrincipals.isEmpty()) { 201 return Collections.EMPTY_SET; 202 } 203 Set principals = realmPrincipals.get(realmName); 204 if (principals == null || principals.isEmpty()) { 205 principals = Collections.EMPTY_SET; 206 } 207 return Collections.unmodifiableSet(principals); 208 } 209 210 public Set<String> getRealmNames() { 211 if (realmPrincipals == null) { 212 return null; 213 } else { 214 return realmPrincipals.keySet(); 215 } 216 } 217 218 public boolean isEmpty() { 219 return realmPrincipals == null || realmPrincipals.isEmpty(); 220 } 221 222 public void clear() { 223 this.cachedToString = null; 224 if (realmPrincipals != null) { 225 realmPrincipals.clear(); 226 realmPrincipals = null; 227 } 228 } 229 230 public Iterator iterator() { 231 return asSet().iterator(); 232 } 233 234 public boolean equals(Object o) { 235 if (o == this) { 236 return true; 237 } 238 if (o instanceof SimplePrincipalCollection) { 239 SimplePrincipalCollection other = (SimplePrincipalCollection) o; 240 return Objects.equals(this.realmPrincipals, other.realmPrincipals); 241 } 242 return false; 243 } 244 245 public int hashCode() { 246 if (this.realmPrincipals != null && !realmPrincipals.isEmpty()) { 247 return realmPrincipals.hashCode(); 248 } 249 return super.hashCode(); 250 } 251 252 /** 253 * Returns a simple string representation suitable for printing. 254 * 255 * @return a simple string representation suitable for printing. 256 * @since 1.0 257 */ 258 public String toString() { 259 if (this.cachedToString == null) { 260 Set<Object> principals = asSet(); 261 if (!CollectionUtils.isEmpty(principals)) { 262 this.cachedToString = StringUtils.toString(principals.toArray()); 263 } else { 264 this.cachedToString = "empty"; 265 } 266 } 267 return this.cachedToString; 268 } 269 270 271 /** 272 * Serialization write support. 273 * <p/> 274 * NOTE: Don't forget to change the serialVersionUID constant at the top of this class 275 * if you make any backwards-incompatible serialization changes!!! 276 * (use the JDK 'serialver' program for this) 277 * 278 * @param out output stream provided by Java serialization 279 * @throws IOException if there is a stream error 280 */ 281 private void writeObject(ObjectOutputStream out) throws IOException { 282 out.defaultWriteObject(); 283 boolean principalsExist = !CollectionUtils.isEmpty(realmPrincipals); 284 out.writeBoolean(principalsExist); 285 if (principalsExist) { 286 out.writeObject(realmPrincipals); 287 } 288 } 289 290 /** 291 * Serialization read support - reads in the Map principals collection if it exists in the 292 * input stream. 293 * <p/> 294 * NOTE: Don't forget to change the serialVersionUID constant at the top of this class 295 * if you make any backwards-incompatible serialization changes!!! 296 * (use the JDK 'serialver' program for this) 297 * 298 * @param in input stream provided by 299 * @throws IOException if there is an input/output problem 300 * @throws ClassNotFoundException if the underlying Map implementation class is not available to the classloader. 301 */ 302 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 303 in.defaultReadObject(); 304 boolean principalsExist = in.readBoolean(); 305 if (principalsExist) { 306 this.realmPrincipals = (Map<String, Set>) in.readObject(); 307 } 308 } 309}