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