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