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     * @since 0.9
035     */
036    @SuppressWarnings({"unchecked"})
037    public 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 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    }