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