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