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