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 */ 021package com.unboundid.ldap.sdk.persist; 022 023 024 025import java.util.UUID; 026 027import com.unboundid.ldap.sdk.DN; 028import com.unboundid.ldap.sdk.DNEntrySource; 029import com.unboundid.ldap.sdk.Entry; 030import com.unboundid.ldap.sdk.LDAPInterface; 031import com.unboundid.ldap.sdk.LDAPException; 032import com.unboundid.util.ThreadSafety; 033import com.unboundid.util.ThreadSafetyLevel; 034 035import static com.unboundid.ldap.sdk.persist.PersistMessages.*; 036import static com.unboundid.util.StaticUtils.*; 037import 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) 046public 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}