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}