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.io.Serializable; 041import java.lang.reflect.Field; 042import java.lang.reflect.Modifier; 043import java.util.List; 044 045import com.unboundid.ldap.sdk.Attribute; 046import com.unboundid.ldap.sdk.Entry; 047import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 048import com.unboundid.util.Debug; 049import com.unboundid.util.NotMutable; 050import com.unboundid.util.StaticUtils; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053import com.unboundid.util.Validator; 054 055import static com.unboundid.ldap.sdk.persist.PersistMessages.*; 056 057 058 059/** 060 * This class provides a data structure that holds information about an 061 * annotated field. 062 */ 063@NotMutable() 064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 065public final class FieldInfo 066 implements Serializable 067{ 068 /** 069 * The serial version UID for this serializable class. 070 */ 071 private static final long serialVersionUID = -5715642176677596417L; 072 073 074 075 // Indicates whether attempts to populate the associated field should fail if 076 // the LDAP attribute has a value that is not valid for the data type of the 077 // field. 078 private final boolean failOnInvalidValue; 079 080 // Indicates whether attempts to populate the associated field should fail if 081 // the LDAP attribute has multiple values but the field can only hold a single 082 // value. 083 private final boolean failOnTooManyValues; 084 085 // Indicates whether the associated field should be included in the entry 086 // created for an add operation. 087 private final boolean includeInAdd; 088 089 // Indicates whether the associated field should be considered for inclusion 090 // in the set of modifications used for modify operations. 091 private final boolean includeInModify; 092 093 // Indicates whether the associated field is part of the RDN. 094 private final boolean includeInRDN; 095 096 // Indicates whether the associated field is required when decoding. 097 private final boolean isRequiredForDecode; 098 099 // Indicates whether the associated field is required when encoding. 100 private final boolean isRequiredForEncode; 101 102 // Indicates whether the associated field should be lazily-loaded. 103 private final boolean lazilyLoad; 104 105 // Indicates whether the associated field supports multiple values. 106 private final boolean supportsMultipleValues; 107 108 // The class that contains the associated field. 109 private final Class<?> containingClass; 110 111 // The field with which this object is associated. 112 private final Field field; 113 114 // The filter usage for the associated field. 115 private final FilterUsage filterUsage; 116 117 // The encoder used for this field. 118 private final ObjectEncoder encoder; 119 120 // The name of the associated attribute type. 121 private final String attributeName; 122 123 // The default values for the field to use for object instantiation. 124 private final String[] defaultDecodeValues; 125 126 // The default values for the field to use for add operations. 127 private final String[] defaultEncodeValues; 128 129 // The names of the object classes for the associated attribute. 130 private final String[] objectClasses; 131 132 133 134 /** 135 * Creates a new field info object from the provided field. 136 * 137 * @param f The field to use to create this object. It must not be 138 * {@code null} and it must be marked with the {@code LDAPField} 139 * annotation. 140 * @param c The class which holds the field. It must not be {@code null} 141 * and it must be marked with the {@code LDAPObject} annotation. 142 * 143 * @throws LDAPPersistException If a problem occurs while processing the 144 * given field. 145 */ 146 FieldInfo(final Field f, final Class<?> c) 147 throws LDAPPersistException 148 { 149 Validator.ensureNotNull(f, c); 150 151 field = f; 152 f.setAccessible(true); 153 154 final LDAPField a = f.getAnnotation(LDAPField.class); 155 if (a == null) 156 { 157 throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_NOT_ANNOTATED.get( 158 f.getName(), c.getName())); 159 } 160 161 final LDAPObject o = c.getAnnotation(LDAPObject.class); 162 if (o == null) 163 { 164 throw new LDAPPersistException(ERR_FIELD_INFO_CLASS_NOT_ANNOTATED.get( 165 c.getName())); 166 } 167 168 containingClass = c; 169 failOnInvalidValue = a.failOnInvalidValue(); 170 includeInRDN = a.inRDN(); 171 includeInAdd = (includeInRDN || a.inAdd()); 172 includeInModify = ((! includeInRDN) && a.inModify()); 173 filterUsage = a.filterUsage(); 174 lazilyLoad = a.lazilyLoad(); 175 isRequiredForDecode = (a.requiredForDecode() && (! lazilyLoad)); 176 isRequiredForEncode = (includeInRDN || a.requiredForEncode()); 177 defaultDecodeValues = a.defaultDecodeValue(); 178 defaultEncodeValues = a.defaultEncodeValue(); 179 180 if (lazilyLoad) 181 { 182 if (defaultDecodeValues.length > 0) 183 { 184 throw new LDAPPersistException( 185 ERR_FIELD_INFO_LAZY_WITH_DEFAULT_DECODE.get(f.getName(), 186 c.getName())); 187 } 188 189 if (defaultEncodeValues.length > 0) 190 { 191 throw new LDAPPersistException( 192 ERR_FIELD_INFO_LAZY_WITH_DEFAULT_ENCODE.get(f.getName(), 193 c.getName())); 194 } 195 196 if (includeInRDN) 197 { 198 throw new LDAPPersistException(ERR_FIELD_INFO_LAZY_IN_RDN.get( 199 f.getName(), c.getName())); 200 } 201 } 202 203 final int modifiers = f.getModifiers(); 204 if (Modifier.isFinal(modifiers)) 205 { 206 throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_FINAL.get( 207 f.getName(), c.getName())); 208 } 209 210 if (Modifier.isStatic(modifiers)) 211 { 212 throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_STATIC.get( 213 f.getName(), c.getName())); 214 } 215 216 try 217 { 218 encoder = a.encoderClass().newInstance(); 219 } 220 catch (final Exception e) 221 { 222 Debug.debugException(e); 223 throw new LDAPPersistException(ERR_FIELD_INFO_CANNOT_GET_ENCODER.get( 224 a.encoderClass().getName(), f.getName(), c.getName(), 225 StaticUtils.getExceptionMessage(e)), e); 226 } 227 228 if (! encoder.supportsType(f.getGenericType())) 229 { 230 throw new LDAPPersistException( 231 ERR_FIELD_INFO_ENCODER_UNSUPPORTED_TYPE.get( 232 encoder.getClass().getName(), f.getName(), c.getName(), 233 f.getGenericType())); 234 } 235 236 supportsMultipleValues = encoder.supportsMultipleValues(f); 237 if (supportsMultipleValues) 238 { 239 failOnTooManyValues = false; 240 } 241 else 242 { 243 failOnTooManyValues = a.failOnTooManyValues(); 244 if (defaultDecodeValues.length > 1) 245 { 246 throw new LDAPPersistException( 247 ERR_FIELD_INFO_UNSUPPORTED_MULTIPLE_DEFAULT_DECODE_VALUES.get( 248 f.getName(), c.getName())); 249 } 250 251 if (defaultEncodeValues.length > 1) 252 { 253 throw new LDAPPersistException( 254 ERR_FIELD_INFO_UNSUPPORTED_MULTIPLE_DEFAULT_ENCODE_VALUES.get( 255 f.getName(), c.getName())); 256 } 257 } 258 259 final String attrName = a.attribute(); 260 if ((attrName == null) || attrName.isEmpty()) 261 { 262 attributeName = f.getName(); 263 } 264 else 265 { 266 attributeName = attrName; 267 } 268 269 final StringBuilder invalidReason = new StringBuilder(); 270 if (! PersistUtils.isValidLDAPName(attributeName, true, invalidReason)) 271 { 272 throw new LDAPPersistException(ERR_FIELD_INFO_INVALID_ATTR_NAME.get( 273 f.getName(), c.getName(), invalidReason.toString())); 274 } 275 276 final String structuralClass; 277 if (o.structuralClass().isEmpty()) 278 { 279 structuralClass = StaticUtils.getUnqualifiedClassName(c); 280 } 281 else 282 { 283 structuralClass = o.structuralClass(); 284 } 285 286 final String[] ocs = a.objectClass(); 287 if ((ocs == null) || (ocs.length == 0)) 288 { 289 objectClasses = new String[] { structuralClass }; 290 } 291 else 292 { 293 objectClasses = ocs; 294 } 295 296 for (final String s : objectClasses) 297 { 298 if (! s.equalsIgnoreCase(structuralClass)) 299 { 300 boolean found = false; 301 for (final String oc : o.auxiliaryClass()) 302 { 303 if (s.equalsIgnoreCase(oc)) 304 { 305 found = true; 306 break; 307 } 308 } 309 310 if (! found) 311 { 312 throw new LDAPPersistException(ERR_FIELD_INFO_INVALID_OC.get( 313 f.getName(), c.getName(), s)); 314 } 315 } 316 } 317 } 318 319 320 321 /** 322 * Retrieves the field with which this object is associated. 323 * 324 * @return The field with which this object is associated. 325 */ 326 public Field getField() 327 { 328 return field; 329 } 330 331 332 333 /** 334 * Retrieves the class that is marked with the {@link LDAPObject} annotation 335 * and contains the associated field. 336 * 337 * @return The class that contains the associated field. 338 */ 339 public Class<?> getContainingClass() 340 { 341 return containingClass; 342 } 343 344 345 346 /** 347 * Indicates whether attempts to initialize an object should fail if the LDAP 348 * attribute has a value that cannot be stored in the associated field. 349 * 350 * @return {@code true} if an exception should be thrown if an LDAP attribute 351 * has a value that cannot be assigned to the associated field, or 352 * {@code false} if the field should remain uninitialized. 353 */ 354 public boolean failOnInvalidValue() 355 { 356 return failOnInvalidValue; 357 } 358 359 360 361 /** 362 * Indicates whether attempts to initialize an object should fail if the 363 * LDAP attribute has multiple values but the associated field can only hold a 364 * single value. Note that the value returned from this method may be 365 * {@code false} even when the annotation has a value of {@code true} if the 366 * associated field supports multiple values. 367 * 368 * @return {@code true} if an exception should be thrown if an attribute has 369 * too many values to hold in the associated field, or {@code false} 370 * if the first value returned should be assigned to the field. 371 */ 372 public boolean failOnTooManyValues() 373 { 374 return failOnTooManyValues; 375 } 376 377 378 379 /** 380 * Indicates whether the associated field should be included in entries 381 * generated for add operations. Note that the value returned from this 382 * method may be {@code true} even when the annotation has a value of 383 * {@code false} if the associated field is to be included in entry RDNs. 384 * 385 * @return {@code true} if the associated field should be included in entries 386 * generated for add operations, or {@code false} if not. 387 */ 388 public boolean includeInAdd() 389 { 390 return includeInAdd; 391 } 392 393 394 395 /** 396 * Indicates whether the associated field should be considered for inclusion 397 * in the set of modifications generated for modify operations. Note that the 398 * value returned from this method may be {@code false} even when the 399 * annotation has a value of {@code true} for the {@code inModify} element if 400 * the associated field is to be included in entry RDNs. 401 * 402 * @return {@code true} if the associated field should be considered for 403 * inclusion in the set of modifications generated for modify 404 * operations, or {@code false} if not. 405 */ 406 public boolean includeInModify() 407 { 408 return includeInModify; 409 } 410 411 412 413 /** 414 * Indicates whether the associated field should be used to generate entry 415 * RDNs. 416 * 417 * @return {@code true} if the associated field should be used to generate 418 * entry RDNs, or {@code false} if not. 419 */ 420 public boolean includeInRDN() 421 { 422 return includeInRDN; 423 } 424 425 426 427 /** 428 * Retrieves the filter usage for the associated field. 429 * 430 * @return The filter usage for the associated field. 431 */ 432 public FilterUsage getFilterUsage() 433 { 434 return filterUsage; 435 } 436 437 438 439 /** 440 * Indicates whether the associated field should be considered required for 441 * decode operations. 442 * 443 * @return {@code true} if the associated field should be considered required 444 * for decode operations, or {@code false} if not. 445 */ 446 public boolean isRequiredForDecode() 447 { 448 return isRequiredForDecode; 449 } 450 451 452 453 /** 454 * Indicates whether the associated field should be considered required for 455 * encode operations. Note that the value returned from this method may be 456 * {@code true} even when the annotation has a value of {@code true} for the 457 * {@code requiredForEncode} element if the associated field is to be included 458 * in entry RDNs. 459 * 460 * @return {@code true} if the associated field should be considered required 461 * for encode operations, or {@code false} if not. 462 */ 463 public boolean isRequiredForEncode() 464 { 465 return isRequiredForEncode; 466 } 467 468 469 470 /** 471 * Indicates whether the associated field should be lazily-loaded. 472 * 473 * @return {@code true} if the associated field should be lazily-loaded, or 474 * {@code false} if not. 475 */ 476 public boolean lazilyLoad() 477 { 478 return lazilyLoad; 479 } 480 481 482 483 /** 484 * Retrieves the encoder that should be used for the associated field. 485 * 486 * @return The encoder that should be used for the associated field. 487 */ 488 public ObjectEncoder getEncoder() 489 { 490 return encoder; 491 } 492 493 494 495 /** 496 * Retrieves the name of the LDAP attribute used to hold values for the 497 * associated field. 498 * 499 * @return The name of the LDAP attribute used to hold values for the 500 * associated field. 501 */ 502 public String getAttributeName() 503 { 504 return attributeName; 505 } 506 507 508 509 /** 510 * Retrieves the set of default values that should be assigned to the 511 * associated field if there are no values for the corresponding attribute in 512 * the LDAP entry. 513 * 514 * @return The set of default values for use when instantiating the object, 515 * or an empty array if no default values are defined. 516 */ 517 public String[] getDefaultDecodeValues() 518 { 519 return defaultDecodeValues; 520 } 521 522 523 524 /** 525 * Retrieves the set of default values that should be used when creating an 526 * entry for an add operation if the associated field does not itself have any 527 * values. 528 * 529 * @return The set of default values for use in add operations, or an empty 530 * array if no default values are defined. 531 */ 532 public String[] getDefaultEncodeValues() 533 { 534 return defaultEncodeValues; 535 } 536 537 538 539 /** 540 * Retrieves the names of the object classes containing the associated 541 * attribute. 542 * 543 * @return The names of the object classes containing the associated 544 * attribute. 545 */ 546 public String[] getObjectClasses() 547 { 548 return objectClasses; 549 } 550 551 552 553 /** 554 * Indicates whether the associated field can hold multiple values. 555 * 556 * @return {@code true} if the associated field can hold multiple values, or 557 * {@code false} if not. 558 */ 559 public boolean supportsMultipleValues() 560 { 561 return supportsMultipleValues; 562 } 563 564 565 566 /** 567 * Constructs a definition for an LDAP attribute type which may be added to 568 * the directory server schema to allow it to hold the value of the associated 569 * field. Note that the object identifier used for the constructed attribute 570 * type definition is not required to be valid or unique. 571 * 572 * @return The constructed attribute type definition. 573 * 574 * @throws LDAPPersistException If the object encoder does not support 575 * encoding values for the associated field 576 * type. 577 */ 578 AttributeTypeDefinition constructAttributeType() 579 throws LDAPPersistException 580 { 581 return constructAttributeType(DefaultOIDAllocator.getInstance()); 582 } 583 584 585 586 /** 587 * Constructs a definition for an LDAP attribute type which may be added to 588 * the directory server schema to allow it to hold the value of the associated 589 * field. Note that the object identifier used for the constructed attribute 590 * type definition is not required to be valid or unique. 591 * 592 * @param a The OID allocator to use to generate the object identifier. It 593 * must not be {@code null}. 594 * 595 * @return The constructed attribute type definition. 596 * 597 * @throws LDAPPersistException If the object encoder does not support 598 * encoding values for the associated field 599 * type. 600 */ 601 AttributeTypeDefinition constructAttributeType(final OIDAllocator a) 602 throws LDAPPersistException 603 { 604 return encoder.constructAttributeType(field, a); 605 } 606 607 608 609 /** 610 * Encodes the value for the associated field from the provided object to an 611 * attribute. 612 * 613 * @param o The object containing the field to be encoded. 614 * @param ignoreRequiredFlag Indicates whether to ignore the value of the 615 * {@code requiredForEncode} setting. If this is 616 * {@code true}, then this method will always 617 * return {@code null} if the field does not have 618 * a value even if this field is marked as 619 * required for encode processing. 620 * 621 * @return The attribute containing the encoded representation of the field 622 * value if it is non-{@code null}, an encoded representation of the 623 * default add values if the associated field is {@code null} but 624 * default values are defined, or {@code null} if the associated 625 * field is {@code null} and there are no default values. 626 * 627 * @throws LDAPPersistException If a problem occurs while encoding the 628 * value of the associated field for the 629 * provided object, or if the field is marked 630 * as required but is {@code null} and does not 631 * have any default add values. 632 */ 633 Attribute encode(final Object o, final boolean ignoreRequiredFlag) 634 throws LDAPPersistException 635 { 636 try 637 { 638 final Object fieldValue = field.get(o); 639 if (fieldValue == null) 640 { 641 if (defaultEncodeValues.length > 0) 642 { 643 return new Attribute(attributeName, defaultEncodeValues); 644 } 645 646 if (isRequiredForEncode && (! ignoreRequiredFlag)) 647 { 648 throw new LDAPPersistException( 649 ERR_FIELD_INFO_MISSING_REQUIRED_VALUE.get(field.getName(), 650 containingClass.getName())); 651 } 652 653 return null; 654 } 655 656 return encoder.encodeFieldValue(field, fieldValue, attributeName); 657 } 658 catch (final LDAPPersistException lpe) 659 { 660 Debug.debugException(lpe); 661 throw lpe; 662 } 663 catch (final Exception e) 664 { 665 Debug.debugException(e); 666 throw new LDAPPersistException( 667 ERR_FIELD_INFO_CANNOT_ENCODE.get(field.getName(), 668 containingClass.getName(), StaticUtils.getExceptionMessage(e)), 669 e); 670 } 671 } 672 673 674 675 /** 676 * Sets the value of the associated field in the given object from the 677 * information contained in the provided attribute. 678 * 679 * @param o The object for which to update the associated 680 * field. 681 * @param e The entry being decoded. 682 * @param failureReasons A list to which information about any failures 683 * may be appended. 684 * 685 * @return {@code true} if the decode process was completely successful, or 686 * {@code false} if there were one or more failures. 687 */ 688 boolean decode(final Object o, final Entry e, 689 final List<String> failureReasons) 690 { 691 boolean successful = true; 692 693 Attribute a = e.getAttribute(attributeName); 694 if ((a == null) || (! a.hasValue())) 695 { 696 if (defaultDecodeValues.length > 0) 697 { 698 a = new Attribute(attributeName, defaultDecodeValues); 699 } 700 else 701 { 702 if (isRequiredForDecode) 703 { 704 successful = false; 705 failureReasons.add(ERR_FIELD_INFO_MISSING_REQUIRED_ATTRIBUTE.get( 706 containingClass.getName(), e.getDN(), attributeName, 707 field.getName())); 708 } 709 710 try 711 { 712 encoder.setNull(field, o); 713 } 714 catch (final LDAPPersistException lpe) 715 { 716 Debug.debugException(lpe); 717 successful = false; 718 failureReasons.add(lpe.getMessage()); 719 } 720 721 return successful; 722 } 723 } 724 725 if (failOnTooManyValues && (a.size() > 1)) 726 { 727 successful = false; 728 failureReasons.add(ERR_FIELD_INFO_FIELD_NOT_MULTIVALUED.get(a.getName(), 729 field.getName(), containingClass.getName())); 730 } 731 732 try 733 { 734 encoder.decodeField(field, o, a); 735 } 736 catch (final LDAPPersistException lpe) 737 { 738 Debug.debugException(lpe); 739 if (failOnInvalidValue) 740 { 741 successful = false; 742 failureReasons.add(lpe.getMessage()); 743 } 744 } 745 746 return successful; 747 } 748}