001 /*
002 * Copyright 2009-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2009-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk.persist;
022
023
024
025 import java.util.UUID;
026
027 import com.unboundid.ldap.sdk.DN;
028 import com.unboundid.ldap.sdk.DNEntrySource;
029 import com.unboundid.ldap.sdk.Entry;
030 import com.unboundid.ldap.sdk.LDAPInterface;
031 import com.unboundid.ldap.sdk.LDAPException;
032 import com.unboundid.util.ThreadSafety;
033 import com.unboundid.util.ThreadSafetyLevel;
034
035 import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
036 import static com.unboundid.util.StaticUtils.*;
037 import static com.unboundid.util.Validator.*;
038
039
040
041 /**
042 * This class provides a set of utilities that may be used in the course of
043 * persistence processing.
044 */
045 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
046 public final class PersistUtils
047 {
048 /**
049 * Prevent this utility class from being instantiated.
050 */
051 private PersistUtils()
052 {
053 // No implementation required.
054 }
055
056
057
058 /**
059 * Indicates whether the provided string could be used as a valid attribute or
060 * object class name. Numeric OIDs will also be considered acceptable.
061 *
062 * @param s The string for which to make the determination.
063 * @param r A buffer to which the unacceptable reason may be appended. It
064 * must not be {@code null}.
065 *
066 * @return {@code true} if the provided string is acceptable for use as an
067 * LDAP attribute or object class name, or {@code false} if not.
068 */
069 public static boolean isValidLDAPName(final String s, final StringBuilder r)
070 {
071 return isValidLDAPName(s, false, r);
072 }
073
074
075
076 /**
077 * Indicates whether the provided string could be used as a valid attribute or
078 * object class name. Numeric OIDs will also be considered acceptable.
079 *
080 * @param s The string for which to make the determination.
081 * @param o Indicates whether the name should be allowed to contain
082 * attribute options (e.g., a semicolon with one or more valid
083 * characters after it).
084 * @param r A buffer to which the unacceptable reason may be appended. It
085 * must not be {@code null}.
086 *
087 * @return {@code true} if the provided string is acceptable for use as an
088 * LDAP attribute or object class name, or {@code false} if not.
089 */
090 public static boolean isValidLDAPName(final String s, final boolean o,
091 final StringBuilder r)
092 {
093 int length;
094 if ((s == null) || ((length = s.length()) == 0))
095 {
096 r.append(ERR_LDAP_NAME_VALIDATOR_EMPTY.get());
097 return false;
098 }
099
100 final String baseName;
101 final int semicolonPos = s.indexOf(';');
102 if (semicolonPos > 0)
103 {
104 if (! o)
105 {
106 r.append(ERR_LDAP_NAME_VALIDATOR_INVALID_CHAR.get(s, ';',
107 semicolonPos));
108 return false;
109 }
110
111 baseName = s.substring(0, semicolonPos);
112 length = baseName.length();
113
114 final String optionsStr = s.substring(semicolonPos+1);
115 if (! isValidOptionSet(baseName, optionsStr, r))
116 {
117 return false;
118 }
119 }
120 else
121 {
122 baseName = s;
123 }
124
125 if (isNumericOID(baseName))
126 {
127 return true;
128 }
129
130 for (int i=0; i < length; i++)
131 {
132 final char c = baseName.charAt(i);
133 if (((c >= 'a') && (c <= 'z')) ||
134 ((c >= 'A') && (c <= 'Z')))
135 {
136 // This will always be acceptable.
137 }
138 else if (((c >= '0') && (c <= '9')) || (c == '-'))
139 {
140 // This will be acceptable for all but the first character.
141 if (i == 0)
142 {
143 r.append(ERR_LDAP_NAME_VALIDATOR_INVALID_FIRST_CHAR.get(s));
144 return false;
145 }
146 }
147 else
148 {
149 r.append(ERR_LDAP_NAME_VALIDATOR_INVALID_CHAR.get(s, c, i));
150 return false;
151 }
152 }
153
154 return true;
155 }
156
157
158
159 /**
160 * Indicates whether the provided string represents a valid set of attribute
161 * options. It should not contain the initial semicolon.
162 *
163 * @param b The base name for the attribute, without the option string or
164 * the semicolon used to delimit the option string from the base
165 * name.
166 * @param o The option string to examine. It must not be {@code null}, and
167 * must not contain the initial semicolon.
168 * @param r A buffer to which the unacceptable reason may be appended. It
169 * must not be {@code null}.
170 *
171 * @return {@code true} if the provided string represents a valid set of
172 * options, or {@code false} if not.
173 */
174 private static boolean isValidOptionSet(final String b, final String o,
175 final StringBuilder r)
176 {
177 boolean lastWasSemicolon = true;
178
179 for (int i=0; i < o.length(); i++)
180 {
181 final char c = o.charAt(i);
182 if (c == ';')
183 {
184 if (lastWasSemicolon)
185 {
186 r.append(
187 ERR_LDAP_NAME_VALIDATOR_OPTION_WITH_CONSECUTIVE_SEMICOLONS.get(
188 b + ';' + o));
189 return false;
190 }
191 else
192 {
193 lastWasSemicolon = true;
194 }
195 }
196 else
197 {
198 lastWasSemicolon = false;
199 if (((c >= 'a') && (c <= 'z')) ||
200 ((c >= 'A') && (c <= 'Z')) ||
201 ((c >= '0') && (c <= '9')) ||
202 (c == '-'))
203 {
204 // This will always be acceptable.
205 }
206 else
207 {
208 r.append(ERR_LDAP_NAME_VALIDATOR_INVALID_OPTION_CHAR.get(
209 (b + ';' + o), c, (b.length() + 1 + i)));
210 return false;
211 }
212 }
213 }
214
215 if (lastWasSemicolon)
216 {
217 r.append(ERR_LDAP_NAME_VALIDATOR_ENDS_WITH_SEMICOLON.get(b + ';' + o));
218 return false;
219 }
220
221 return true;
222 }
223
224
225
226 /**
227 * Indicates whether the provided string could be used as a valid Java
228 * identifier. The identifier must begin with an ASCII letter or underscore,
229 * and must contain only ASCII letters, ASCII digits, and the underscore
230 * character. Even though a dollar sign is technically allowed, it will not
231 * be considered valid for the purpose of this method. Similarly, even though
232 * Java keywords are not allowed, they will not be rejected by this method.
233 *
234 * @param s The string for which to make the determination. It must not be
235 * {@code null}.
236 * @param r A buffer to which the unacceptable reason may be appended. It
237 * must not be {@code null}.
238 *
239 * @return {@code true} if the provided string is acceptable for use as a
240 * Java identifier, or {@code false} if not.
241 */
242 public static boolean isValidJavaIdentifier(final String s,
243 final StringBuilder r)
244 {
245 final int length = s.length();
246 for (int i=0; i < length; i++)
247 {
248 final char c = s.charAt(i);
249 if (((c >= 'a') && (c <= 'z')) ||
250 ((c >= 'A') && (c <= 'Z')) ||
251 (c == '_'))
252 {
253 // This will always be acceptable.
254 }
255 else if ((c >= '0') && (c <= '9'))
256 {
257 if (i == 0)
258 {
259 r.append(ERR_JAVA_NAME_VALIDATOR_INVALID_FIRST_CHAR_DIGIT.get(s));
260 return false;
261 }
262 }
263 else
264 {
265 r.append(ERR_JAVA_NAME_VALIDATOR_INVALID_CHAR.get(s, c, i));
266 return false;
267 }
268 }
269
270 return true;
271 }
272
273
274
275 /**
276 * Transforms the provided string if necessary so that it may be used as a
277 * valid Java identifier. If the provided string is already a valid Java
278 * identifier, then it will be returned as-is. Otherwise, it will be
279 * transformed to make it more suitable.
280 *
281 * @param s The attribute or object class name to be converted to a Java
282 * identifier.
283 *
284 * @return A string that may be used as a valid Java identifier.
285 */
286 public static String toJavaIdentifier(final String s)
287 {
288 final int length;
289 if ((s == null) || ((length = s.length()) == 0))
290 {
291 // This will be ugly, but safe.
292 return toJavaIdentifier(UUID.randomUUID().toString());
293 }
294
295 boolean nextUpper = false;
296 final StringBuilder b = new StringBuilder(length);
297 for (int i=0; i < length; i++)
298 {
299 final char c = s.charAt(i);
300 if (((c >= 'a') && (c <= 'z')) ||
301 ((c >= 'A') && (c <= 'Z')))
302 {
303 if (nextUpper)
304 {
305 b.append(Character.toUpperCase(c));
306 }
307 else
308 {
309 b.append(c);
310 }
311
312 nextUpper = false;
313 }
314 else if ((c >= '0') && (c <= '9'))
315 {
316 if (i == 0)
317 {
318 // Java identifiers can't begin with a digit, but they can begin with
319 // an underscore followed by a digit, so we'll use that instead.
320 b.append('_');
321 }
322
323 b.append(c);
324 nextUpper = false;
325 }
326 else
327 {
328 // If the provided string was a valid LDAP attribute or object class
329 // name, then this should be a dash, but we'll be safe and take the same
330 // action for any remaining character.
331 nextUpper = true;
332 }
333 }
334
335 if (b.length() == 0)
336 {
337 // This should only happen if the provided string wasn't a valid LDAP
338 // attribute or object class name to start with.
339 return toJavaIdentifier(UUID.randomUUID().toString());
340 }
341
342 return b.toString();
343 }
344
345
346
347 /**
348 * Retrieves the entry with the specified DN and decodes it as an object of
349 * the specified type.
350 *
351 * @param <T> The type of object as which to decode the entry.
352 *
353 * @param dn The DN of the entry to retrieve. It must not be
354 * {@code null}.
355 * @param type The type of object as which the entry should be decoded. It
356 * must not be {@code null}, and the class must be marked with
357 * the {@link LDAPObject} annotation type.
358 * @param conn The connection that should be used to retrieve the entry. It
359 * must not be {@code null}.
360 *
361 * @return The object decoded from the specified entry, or {@code null} if
362 * the entry cannot be retrieved (e.g., because it does not exist or
363 * is not readable by the authenticated user).
364 *
365 * @throws LDAPException If a problem occurs while trying to retrieve the
366 * entry or decode it as the specified type of object.
367 */
368 public static <T> T getEntryAsObject(final DN dn, final Class<T> type,
369 final LDAPInterface conn)
370 throws LDAPException
371 {
372 ensureNotNull(dn, type, conn);
373
374 final LDAPPersister<T> p = LDAPPersister.getInstance(type);
375
376 final Entry e = conn.getEntry(dn.toString(),
377 p.getObjectHandler().getAttributesToRequest());
378 if (e == null)
379 {
380 return null;
381 }
382
383 return p.decode(e);
384 }
385
386
387
388 /**
389 * Retrieves and decodes the indicated entries as objects of the specified
390 * type.
391 *
392 * @param <T> The type of object as which to decode the entries.
393 *
394 * @param dns The DNs of the entries to retrieve. It must not be
395 * {@code null}.
396 * @param type The type of object as which the entries should be decoded.
397 * It must not be {@code null}, and the class must be marked
398 * with the {@link LDAPObject} annotation type.
399 * @param conn The connection that should be used to retrieve the entries.
400 * It must not be {@code null}.
401 *
402 * @return A {@code PersistedObjects} result that may be used to access the
403 * objects decoded from the provided set of DNs.
404 *
405 * @throws LDAPPersistException If the requested type cannot be used with
406 * the LDAP SDK persistence framework.
407 */
408 public static <T> PersistedObjects<T> getEntriesAsObjects(final DN[] dns,
409 final Class<T> type,
410 final LDAPInterface conn)
411 throws LDAPPersistException
412 {
413 ensureNotNull(dns, type, conn);
414
415 final LDAPPersister<T> p = LDAPPersister.getInstance(type);
416
417 final DNEntrySource entrySource = new DNEntrySource(conn, dns,
418 p.getObjectHandler().getAttributesToRequest());
419 return new PersistedObjects<T>(p, entrySource);
420 }
421 }