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.util.StringUtils;
23  
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.util.*;
28  
29  
30  /**
31   * A simple implementation of the {@link MutablePrincipalCollection} interface that tracks principals internally
32   * by storing them in a {@link LinkedHashMap}.
33   *
34   * @since 0.9
35   */
36  @SuppressWarnings({"unchecked"})
37  public class SimplePrincipalCollection implements MutablePrincipalCollection {
38  
39      // Serialization reminder:
40      // You _MUST_ change this number if you introduce a change to this class
41      // that is NOT serialization backwards compatible.  Serialization-compatible
42      // changes do not require a change to this number.  If you need to generate
43      // a new number in this case, use the JDK's 'serialver' program to generate it.
44      private static final long serialVersionUID = -6305224034025797558L;
45  
46      //TODO - complete JavaDoc
47  
48      private Map<String, Set> realmPrincipals;
49  
50      private transient String cachedToString; //cached toString() result, as this can be printed many times in logging
51  
52      public SimplePrincipalCollection() {
53      }
54  
55      public SimplePrincipalCollection(Object principal, String realmName) {
56          if (principal instanceof Collection) {
57              addAll((Collection) principal, realmName);
58          } else {
59              add(principal, realmName);
60          }
61      }
62  
63      public SimplePrincipalCollection(Collection principals, String realmName) {
64          addAll(principals, realmName);
65      }
66  
67      public SimplePrincipalCollection(PrincipalCollection principals) {
68          addAll(principals);
69      }
70  
71      protected Collection getPrincipalsLazy(String realmName) {
72          if (realmPrincipals == null) {
73              realmPrincipals = new LinkedHashMap<String, Set>();
74          }
75          Set principals = realmPrincipals.get(realmName);
76          if (principals == null) {
77              principals = new LinkedHashSet();
78              realmPrincipals.put(realmName, principals);
79          }
80          return principals;
81      }
82  
83      /**
84       * Returns the first available principal from any of the {@code Realm} principals, or {@code null} if there are
85       * no principals yet.
86       * <p/>
87       * The 'first available principal' is interpreted as the principal that would be returned by
88       * <code>{@link #iterator() iterator()}.{@link java.util.Iterator#next() next()}.</code>
89       *
90       * @inheritDoc
91       */
92      public Object getPrimaryPrincipal() {
93          if (isEmpty()) {
94              return null;
95          }
96          return iterator().next();
97      }
98  
99      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 serializatoin 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 serializatoin 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 }