1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package no.feide.moria.directory.index;
21
22 import java.io.Serializable;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.Iterator;
27
28 /***
29 * A serializable index implementation, used for offline generation of a new
30 * index.
31 */
32 public class SerializableIndex
33 implements Serializable, DirectoryManagerIndex {
34
35 /*** Serial version identifier. */
36 static final long serialVersionUID = -3356791609795577197L;
37
38 /***
39 * Internal list of associations; that is, the mapping between logical ID
40 * realms (as <code>String</code>s) - following the 'at' character - and
41 * search base references (as <code>String</code> arrays).
42 */
43 private HashMap associations = new HashMap();
44
45 /***
46 * Internal list of exceptions to the associations; that is, explicitly
47 * indexed logical IDs (as <code>String</code>s) to full external
48 * references (as <code>String</code> s).
49 */
50 private HashMap exceptions = new HashMap();
51
52 /***
53 * Internal list of realms for each exception. The list should have the same
54 * keys as <code>exceptions</code>, but the elements of this list give
55 * the explicit realm (as <code>String</code>s) for each exception.
56 */
57 private HashMap realms = new HashMap();
58
59 /***
60 * Internal list of usernames for each association/exception.
61 */
62 private HashMap usernames = new HashMap();
63
64 /***
65 * Internal list of passwords for each association/exception.
66 */
67 private HashMap passwords = new HashMap();
68
69
70 /***
71 * Checks whether two index instances are equal. <br>
72 * <br>
73 * Note that for convenience (and/or laziness) this method relies on the
74 * <code>String</code> representation as given by <code>toString()</code>.
75 * @param obj
76 * The other <code>SerializableIndex</code> object to compare
77 * to.
78 * @return <code>true</code> if two <code>SerializableIndex</code>
79 * objects are equal, otherwise <code>false</code>. Two instances
80 * are equal if and only if their lists of associations, exceptions
81 * and realms are equal.
82 * @see java.lang.Object#equals(java.lang.Object)
83 * @see #toString()
84 */
85 public final boolean equals(final Object obj) {
86
87
88 if (obj.getClass() != this.getClass())
89 return false;
90 final SerializableIndex other = (SerializableIndex) obj;
91
92
93 if (!associations.keySet().equals(other.associations.keySet()))
94 return false;
95 Iterator keys = associations.keySet().iterator();
96 while (keys.hasNext()) {
97 String key = (String) keys.next();
98 String[] values = (String[]) associations.get(key);
99 ArrayList myValues = new ArrayList(values.length);
100 for (int i = 0; i < values.length; i++)
101 myValues.add(values[i]);
102 values = (String[]) other.associations.get(key);
103 ArrayList otherValues = new ArrayList(values.length);
104 for (int i = 0; i < values.length; i++)
105 otherValues.add(values[i]);
106 if (!myValues.equals(otherValues))
107 return false;
108 }
109
110
111
112 if (!exceptions.equals(other.exceptions))
113 return false;
114 if (!realms.equals(other.realms))
115 return false;
116 if (!usernames.equals(other.usernames))
117 return false;
118 if (!passwords.equals(other.passwords))
119 return false;
120
121
122 return true;
123 }
124
125
126 /***
127 * Generates a hashCode. Dummy implementation.
128 * @return The hashcode.
129 * @throws UnsupportedOperationException
130 * Always.
131 */
132 public final int hashCode() {
133
134 throw new UnsupportedOperationException();
135 }
136
137
138 /***
139 * Looks up an element reference from the index based on its logical ID
140 * (typically username). <br>
141 * <br>
142 * Note that looking up in the association list requires the logical ID to
143 * be on the form <code>identifier-at-realm</code>, similar to an email
144 * address. This is <em>not</em> a requirement for looking up references
145 * in the exception list, and therefore the 'at' character is not required
146 * in the logical ID.
147 * @param id
148 * The logical identifier to look up.
149 * @return One or more references matching the given identifier, or
150 * <code>null</code> if no such reference was found.
151 * @see DirectoryManagerIndex#getReferences(String)
152 */
153 public final IndexedReference[] getReferences(final String id) {
154
155
156 if (id == null)
157 return null;
158
159 ArrayList newReferences = new ArrayList();
160
161
162
163 if (exceptions.containsKey(id))
164 newReferences.add(new IndexedReference(new String[] {(String) exceptions.get(id)}, new String[] {""}, new String[] {""}, true));
165
166
167 int i = id.lastIndexOf('@');
168 if ((i > 0) && (associations.containsKey(id.substring(i + 1)))) {
169
170
171 String[] gatheredAssociations = (String[]) associations.get(id.substring(i + 1));
172 ArrayList gatheredUsernames = new ArrayList(gatheredAssociations.length);
173 ArrayList gatheredPasswords = new ArrayList(gatheredAssociations.length);
174 for (int j = 0; j < gatheredAssociations.length; j++) {
175 gatheredUsernames.add(usernames.get(gatheredAssociations[j]));
176 gatheredPasswords.add(passwords.get(gatheredAssociations[j]));
177 }
178 newReferences.add(new IndexedReference(gatheredAssociations, (String[]) gatheredUsernames.toArray(new String[] {}), (String[]) gatheredPasswords.toArray(new String[] {}), false));
179 }
180
181
182 if (newReferences.size() == 0)
183 return null;
184 return (IndexedReference[]) newReferences.toArray(new IndexedReference[] {});
185
186 }
187
188
189 /***
190 * Looks up which realm a given identifier belongs to.
191 * @param id
192 * The logical identifier to get realm for.
193 * @return The resolved realm for this identifier, or <code>null</code> if
194 * no such realm could be found.
195 * @see DirectoryManagerIndex#getRealm(String)
196 */
197 public final String getRealm(final String id) {
198
199
200
201 if (realms.containsKey(id))
202 return (String) realms.get(id);
203
204
205 int i = id.lastIndexOf('@');
206 if ((i > 0) && (associations.containsKey(id.substring(i + 1))))
207 return id.substring(i + 1);
208
209
210 return null;
211
212 }
213
214
215 /***
216 * Adds a new realm-to-base association to the index. Any modification of
217 * the index will result in any existing association with the same realm to
218 * be appended with the new realm. <br>
219 * <br>
220 * Note that this method does <em>not</em> check for duplicate
221 * associations (associations between one realm and two identical bases).
222 * @param realm
223 * The realm (typically user realm) related to this base. Cannot
224 * be <code>null</code>.
225 * @param base
226 * The association. In practical use this will be an LDAP search
227 * base similar to
228 * <code>ldap://some.ldap.server:636/dc=search,dc=base</code>.
229 * Cannot be <code>null</code>.
230 * @param username
231 * The username to be used when performing searches on this
232 * association's base. May not be <code>null</code>.
233 * @param password
234 * The password to be used when performing searches on this
235 * association's base. May not be <code>null</code>.
236 * @throws IllegalArgumentException
237 * If either <code>realm</code>,<code>base</code>,
238 * <code>username</code>, or <code>password</code> is
239 * <code>null</code>.
240 */
241 public final void addAssociation(final String realm,
242 final String base,
243 final String username,
244 final String password) {
245
246
247 if (realm == null)
248 throw new IllegalArgumentException("Realm cannot be NULL");
249 if (base == null)
250 throw new IllegalArgumentException("Base cannot be NULL");
251 if (username == null)
252 throw new IllegalArgumentException("Username cannot be NULL");
253 if (password == null)
254 throw new IllegalArgumentException("Password cannot be NULL");
255
256
257 if (associations.containsKey(realm)) {
258
259
260 ArrayList bases = new ArrayList(Arrays.asList((String[]) associations.get(realm)));
261 bases.add(base);
262 associations.put(realm, bases.toArray(new String[] {}));
263
264 } else {
265
266
267 associations.put(realm, new String[] {base});
268
269 }
270
271
272 usernames.put(base, username);
273 passwords.put(base, password);
274
275 }
276
277
278 /***
279 * Add a new search exception (exception to the basic rule of realm-to-base
280 * associations) to this index. Any modifications to an already existing
281 * exception will result in the old references being replaced.
282 * @param id
283 * The identifier for this exception, typically a user ID. Cannot
284 * be <code>null</code>.
285 * @param reference
286 * The reference. In practical use this will be an LDAP element
287 * reference similar to
288 * <code>ldap://some.ldap.server:636/uid=id,dc=search,dc=base</code>.
289 * Cannot be <code>null</code>.
290 * @param realm
291 * The actual realm of the reference, which may not be given by
292 * the identifier (on the form <i>user@realm </i>, for example).
293 * Since moving between realms while keeping an unchanged
294 * identifier is possible, this must be taken into account.
295 * @throws IllegalArgumentException
296 * If either <code>id</code> or <code>reference</code> is
297 * <code>null</code>.
298 */
299 public final void addException(final String id,
300 final String reference,
301 final String realm) {
302
303
304 if (id == null)
305 throw new IllegalArgumentException("ID cannot be NULL");
306 if (reference == null)
307 throw new IllegalArgumentException("Reference cannot be NULL");
308
309 exceptions.put(id, reference);
310 realms.put(id, realm);
311
312 }
313
314
315 /***
316 * Gives a string representation of the object, for visual debugging.
317 * @return The object represented as a <code>String</code>, includes
318 * separate lists of associations and exceptions.
319 */
320 public final String toString() {
321
322
323 String s = "\tAssociations: {";
324 Iterator associationKeys = associations.keySet().iterator();
325 while (associationKeys.hasNext()) {
326 String[] bases = (String[]) associations.get(associationKeys.next());
327 for (int i = 0; i < bases.length; i++)
328 s = s + bases[i] + " '" + usernames.get(bases[i]) + "'/'" + passwords.get(bases[i]) + "'\n\t ";
329 }
330 s = s.substring(0, s.length() - 17) + "}";
331
332
333 return s = s + "\n\tExceptions: " + exceptions.toString().replaceAll(", ", "\n\t ");
334
335 }
336
337
338 /***
339 * Return the username associated with a given base.
340 * @param base
341 * The search base. May not be <code>null</code> or an empty
342 * string.
343 * @return The username associated with <code>base</code>. May be an
344 * empty string, but will never be <code>null</code> even though
345 * the <code>base</code> does not exist.
346 * @throws IllegalArgumentException
347 * If <code>base</code> is <code>null</code> or an empty
348 * string.
349 */
350 public final String getUsername(final String base) throws IllegalArgumentException {
351
352
353 if ((base == null) || (base.length() == 0))
354 throw new IllegalArgumentException("Base must be a non-empty string");
355
356
357
358 if (usernames.containsKey(base))
359 return (String) usernames.get(base);
360 return "";
361
362 }
363
364
365 /***
366 * Return the password associated with a given base.
367 * @param base
368 * The search base. May not be <code>null</code> or an empty
369 * string.
370 * @return The password associated with <code>base</code>. May be an
371 * empty string, but will never be <code>null</code> even though
372 * the <code>base</code> does not exist.
373 * @throws IllegalArgumentException
374 * If <code>base</code> is <code>null</code> or an empty
375 * string.
376 */
377 public final String getPassword(final String base) throws IllegalArgumentException {
378
379
380 if ((base == null) || (base.length() == 0))
381 throw new IllegalArgumentException("Base must be a non-empty string");
382
383
384
385 if (passwords.containsKey(base))
386 return (String) passwords.get(base);
387 return "";
388
389 }
390
391 }