View Javadoc
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  package org.apache.shiro.subject;
20  
21  import org.apache.shiro.util.CollectionUtils;
22  import org.apache.shiro.lang.util.StringUtils;
23  
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.Iterator;
31  import java.util.LinkedHashMap;
32  import java.util.LinkedHashSet;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Objects;
36  import java.util.Set;
37  
38  /**
39   * A simple implementation of the {@link MutablePrincipalCollection} interface that tracks principals internally
40   * by storing them in a {@link LinkedHashMap}.
41   *
42   * @since 0.9
43   */
44  @SuppressWarnings({"unchecked"})
45  public class SimplePrincipalCollection implements 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      private static final long serialVersionUID = -6305224034025797558L;
53  
54      //TODO - complete JavaDoc
55      private Map<String, Set> realmPrincipals;
56  
57      //cached toString() result, as this can be printed many times in logging
58      private transient String cachedToString;
59  
60      public SimplePrincipalCollection() {
61      }
62  
63      public SimplePrincipalCollection(Object principal, String realmName) {
64          if (principal instanceof Collection) {
65              addAll((Collection) principal, realmName);
66          } else {
67              add(principal, realmName);
68          }
69      }
70  
71      public SimplePrincipalCollection(Collection principals, String realmName) {
72          addAll(principals, realmName);
73      }
74  
75      public SimplePrincipalCollection(PrincipalCollection principals) {
76          addAll(principals);
77      }
78  
79      protected Collection getPrincipalsLazy(String realmName) {
80          if (realmPrincipals == null) {
81              realmPrincipals = new LinkedHashMap<String, Set>();
82          }
83          Set principals = realmPrincipals.get(realmName);
84          if (principals == null) {
85              principals = new LinkedHashSet();
86              realmPrincipals.put(realmName, principals);
87          }
88          return principals;
89      }
90  
91      /**
92       * Returns the first available principal from any of the {@code Realm} principals, or {@code null} if there are
93       * no principals yet.
94       * <p/>
95       * The 'first available principal' is interpreted as the principal that would be returned by
96       * <code>{@link #iterator() iterator()}.{@link java.util.Iterator#next() next()}.</code>
97       *
98       * @inheritDoc
99       */
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 }