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 module hunt.shiro.subject.SimplePrincipalCollection;
20 
21 import hunt.shiro.subject.PrincipalCollection;
22 
23 import hunt.shiro.util.CollectionUtils;
24 // import hunt.shiro.util.StringUtils;
25 import hunt.shiro.subject.MutablePrincipalCollection;
26 
27 import hunt.collection;
28 import hunt.logging.Logger;
29 import hunt.Exceptions;
30 import hunt.String;
31 import hunt.text.StringUtils;
32 
33 import std.array;
34 import std.range;
35 // import java.io.IOException;
36 // import java.io.ObjectInputStream;
37 // import java.io.ObjectOutputStream;
38 
39 
40 /**
41  * A simple implementation of the {@link MutablePrincipalCollection} interface that tracks principals internally
42  * by storing them in a {@link LinkedHashMap}.
43  *
44  */
45 class SimplePrincipalCollection : 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 
53 
54     private Map!(string, Set!Object) realmPrincipals;
55 
56     private string cachedToString; //cached toString() result, as this can be printed many times in logging
57 
58     this() {
59     }
60 
61     this(string principal, string realmName) {
62         this(new String(principal), realmName);
63     }
64 
65     this(Object principal, string realmName) {
66         Collection!Object c = cast(Collection!Object) principal;
67         if (c !is null) {
68             addAll(c, realmName);
69         } else {
70             add(principal, realmName);
71         }
72     }
73 
74     this(Collection!Object principals, string realmName) {
75         addAll(principals, realmName);
76     }
77 
78     this(PrincipalCollection principals) {
79         addAll(principals);
80     }
81 
82     protected Collection!Object getPrincipalsLazy(string realmName) {
83         if (realmPrincipals is null) {
84             realmPrincipals = new LinkedHashMap!(string, Set!Object)();
85         }
86 
87         Set!Object principals = null; 
88         try {
89             if(realmPrincipals.containsKey(realmName)) {
90                 principals = realmPrincipals.get(realmName);
91             }
92         } catch(Exception ex) {
93             version(HUNT_DEBUG) warning(ex.msg);
94         }
95 
96         if (principals is null) {
97             principals = new LinkedHashSet!Object();
98             realmPrincipals.put(realmName, principals);
99         }
100         return principals;
101     }
102 
103     /**
104      * Returns the first available principal from any of the {@code Realm} principals, or {@code null} if there are
105      * no principals yet.
106      * <p/>
107      * The 'first available principal' is interpreted as the principal that would be returned by
108      * <code>{@link #iterator() iterator()}.{@link java.util.Iterator#next() next()}.</code>
109      *
110      * @inheritDoc
111      */
112     Object getPrimaryPrincipal() {
113         if (isEmpty()) {
114             return null;
115         }
116         return asSet().toArray()[0];
117     }
118     
119     void add(string principal, string realmName) {
120         add(new String(principal), realmName);
121     }
122 
123     void add(Object principal, string realmName) {
124         if (realmName is null) {
125             throw new IllegalArgumentException("realmName argument cannot be null.");
126         }
127         if (principal is null) {
128             throw new IllegalArgumentException("principal argument cannot be null.");
129         }
130         this.cachedToString = null;
131         getPrincipalsLazy(realmName).add(principal);
132     }
133 
134      void addAll(Collection!Object principals, string realmName) {
135         if (realmName is null) {
136             throw new IllegalArgumentException("realmName argument cannot be null.");
137         }
138         if (principals is null) {
139             throw new IllegalArgumentException("principals argument cannot be null.");
140         }
141         if (principals.isEmpty()) {
142             throw new IllegalArgumentException("principals argument cannot be an empty collection.");
143         }
144         this.cachedToString = null;
145         getPrincipalsLazy(realmName).addAll(principals);
146     }
147 
148      void addAll(PrincipalCollection principals) {
149         if (principals.getRealmNames() !is null) {
150             foreach(string realmName ; principals.getRealmNames()) {
151                 foreach(Object principal ; principals.fromRealm(realmName)) {
152                     add(principal, realmName);
153                 }
154             }
155         }
156     }
157 
158     T oneByType(T)() if(is(T == class) || is(T == interface)) {
159         if (realmPrincipals is null || realmPrincipals.isEmpty()) {
160             return null;
161         }
162         Set!(Object)[] values = realmPrincipals.values();
163         foreach(Set!Object set ; values) {
164             foreach(Object o ; set) {
165                 T v = cast(T)o;
166                 if(o !is null && v !is null)    
167                     return v;
168             }
169         }
170         return null;
171     }
172 
173     Collection!(T) byType(T)() if(is(T == class) || is(T == interface)) {
174         if (realmPrincipals is null || realmPrincipals.isEmpty()) {
175             return Collections.emptySet!T;
176         }
177         Set!(T) typed = new LinkedHashSet!(T)();
178         Set!(Object)[] values = realmPrincipals.values();
179         foreach(Set set ; values) {
180             foreach(Object o ; set) {
181                 T v = cast(T)o;
182                 if(o !is null && v !is null) {
183                     typed.add(v);
184                 }
185             }
186         }
187         if (typed.isEmpty()) {
188             return Collections.emptySet!T;
189         }
190         return typed;
191     }
192 
193     List!Object asList() {
194         Set!Object all = asSet();
195         if (all.isEmpty()) {
196             return Collections.emptyList!Object();
197         }
198         return new ArrayList!Object(all);
199     }
200 
201     Set!Object asSet() {
202         if (realmPrincipals is null || realmPrincipals.isEmpty()) {
203             return Collections.emptySet!(Object)();
204         }
205 
206         Set!Object aggregated = new LinkedHashSet!Object();
207         foreach(Set!(Object) set ; realmPrincipals.byValue()) {
208             aggregated.addAll(set);
209         }
210         if (aggregated.isEmpty()) {
211             return Collections.emptySet!(Object)();
212         }
213         return aggregated; 
214     }
215 
216     Object[] fromRealm(string realmName) {
217         if (realmPrincipals is null || realmPrincipals.isEmpty()) {
218             return null;
219         }
220         Set!Object principals = realmPrincipals.get(realmName);
221         if (principals is null || principals.isEmpty()) {
222             return null;
223         } else {
224             return principals.toArray();
225         }
226     }
227 
228     string[] getRealmNames() {
229         if (realmPrincipals is null) {
230             return null;
231         } else {
232             return realmPrincipals.byKey.array();
233         }
234     }
235 
236      bool isEmpty() {
237         return realmPrincipals is null || realmPrincipals.isEmpty();
238     }
239 
240      void clear() {
241         this.cachedToString = null;
242         if (realmPrincipals !is null) {
243             realmPrincipals.clear();
244             realmPrincipals = null;
245         }
246     }
247 
248     //  Iterator iterator() {
249     //     return asSet().iterator();
250     // }
251 
252     override bool opEquals(Object o) {
253         if (o is this) {
254             return true;
255         }
256         
257         SimplePrincipalCollection other = cast(SimplePrincipalCollection) o;
258         if (other !is null) {
259             return this.realmPrincipals !is null ? 
260                 this.realmPrincipals == other.realmPrincipals :
261                 other.realmPrincipals is null;
262         }
263         return false;
264     }
265 
266     override size_t toHash() @trusted nothrow {
267         try {
268             if (this.realmPrincipals !is null && !realmPrincipals.isEmpty()) {
269                 return realmPrincipals.toHash();
270             }
271         } catch(Exception ex) {
272             warning(ex.msg);
273         }
274         return super.toHash();
275     }
276 
277     /**
278      * Returns a simple string representation suitable for printing.
279      *
280      * @return a simple string representation suitable for printing.
281      */
282     override string toString() {
283         if (this.cachedToString is null) {
284             Set!(Object) principals = asSet();
285             if (!CollectionUtils.isEmpty(principals)) {
286                 this.cachedToString = StringUtils.toCommaDelimitedString(principals.toArray());
287             } else {
288                 this.cachedToString = "empty";
289             }
290         }
291         return this.cachedToString;
292     }
293 
294 
295     /**
296      * Serialization write support.
297      * <p/>
298      * NOTE: Don't forget to change the serialVersionUID constant at the top of this class
299      * if you make any backwards-incompatible serialization changes!!!
300      * (use the JDK 'serialver' program for this)
301      *
302      * @param out output stream provided by Java serialization
303      * @throws IOException if there is a stream error
304      */
305     // private void writeObject(ObjectOutputStream out){
306     //     out.defaultWriteObject();
307     //     bool principalsExist = !CollectionUtils.isEmpty(realmPrincipals);
308     //     out.writebool(principalsExist);
309     //     if (principalsExist) {
310     //         out.writeObject(realmPrincipals);
311     //     }
312     // }
313 
314     /**
315      * Serialization read support - reads in the Map principals collection if it exists in the
316      * input stream.
317      * <p/>
318      * NOTE: Don't forget to change the serialVersionUID constant at the top of this class
319      * if you make any backwards-incompatible serialization changes!!!
320      * (use the JDK 'serialver' program for this)
321      *
322      * @param in input stream provided by
323      * @throws IOException            if there is an input/output problem
324      * @throws ClassNotFoundException if the underlying Map implementation class is not available to the classloader.
325      */
326     // private void readObject(ObjectInputStream in){
327     //     in.defaultReadObject();
328     //     bool principalsExist = in.readbool();
329     //     if (principalsExist) {
330     //         this.realmPrincipals = (Map!(string, Set)) in.readObject();
331     //     }
332     // }
333 
334 
335     int opApply(scope int delegate(ref Object) dg) {
336         
337         int result = 0;
338         foreach(Object obj; asSet()) {
339             result = dg(obj);
340         }
341         return result;
342     }
343 }