001    /*
002     * Copyright 2009-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2015 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     */
021    package com.unboundid.ldap.sdk.persist;
022    
023    
024    
025    import java.io.Serializable;
026    import java.lang.reflect.Field;
027    import java.lang.reflect.Modifier;
028    import java.util.List;
029    
030    import com.unboundid.ldap.sdk.Attribute;
031    import com.unboundid.ldap.sdk.Entry;
032    import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
033    import com.unboundid.util.NotMutable;
034    import com.unboundid.util.ThreadSafety;
035    import com.unboundid.util.ThreadSafetyLevel;
036    
037    import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
038    import static com.unboundid.util.Debug.*;
039    import static com.unboundid.util.StaticUtils.*;
040    import 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)
050    public 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    }