All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.persist.FieldInfo Maven / Gradle / Ivy

/*
 * Copyright 2009-2024 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2009-2024 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2009-2024 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.ldap.sdk.persist;



import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;

import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;

import static com.unboundid.ldap.sdk.persist.PersistMessages.*;



/**
 * This class provides a data structure that holds information about an
 * annotated field.
 */
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class FieldInfo
       implements Serializable
{
  /**
   * The serial version UID for this serializable class.
   */
  private static final long serialVersionUID = -5715642176677596417L;



  // Indicates whether attempts to populate the associated field should fail if
  // the LDAP attribute has a value that is not valid for the data type of the
  // field.
  private final boolean failOnInvalidValue;

  // Indicates whether attempts to populate the associated field should fail if
  // the LDAP attribute has multiple values but the field can only hold a single
  // value.
  private final boolean failOnTooManyValues;

  // Indicates whether the associated field should be included in the entry
  // created for an add operation.
  private final boolean includeInAdd;

  // Indicates whether the associated field should be considered for inclusion
  // in the set of modifications used for modify operations.
  private final boolean includeInModify;

  // Indicates whether the associated field is part of the RDN.
  private final boolean includeInRDN;

  // Indicates whether the associated field is required when decoding.
  private final boolean isRequiredForDecode;

  // Indicates whether the associated field is required when encoding.
  private final boolean isRequiredForEncode;

  // Indicates whether the associated field should be lazily-loaded.
  private final boolean lazilyLoad;

  // Indicates whether the associated field supports multiple values.
  private final boolean supportsMultipleValues;

  // The class that contains the associated field.
  @NotNull private final Class containingClass;

  // The field with which this object is associated.
  @NotNull private final Field field;

  // The filter usage for the associated field.
  @NotNull private final FilterUsage filterUsage;

  // The encoder used for this field.
  @NotNull private final ObjectEncoder encoder;

  // The name of the associated attribute type.
  @NotNull private final String attributeName;

  // The default values for the field to use for object instantiation.
  @NotNull private final String[] defaultDecodeValues;

  // The default values for the field to use for add operations.
  @NotNull private final String[] defaultEncodeValues;

  // The names of the object classes for the associated attribute.
  @NotNull private final String[] objectClasses;



  /**
   * Creates a new field info object from the provided field.
   *
   * @param  f  The field to use to create this object.  It must not be
   *            {@code null} and it must be marked with the {@code LDAPField}
   *            annotation.
   * @param  c  The class which holds the field.  It must not be {@code null}
   *            and it must be marked with the {@code LDAPObject} annotation.
   *
   * @throws  LDAPPersistException  If a problem occurs while processing the
   *                                given field.
   */
  FieldInfo(@NotNull final Field f, @NotNull final Class c)
       throws LDAPPersistException
  {
    Validator.ensureNotNull(f, c);

    field = f;
    f.setAccessible(true);

    final LDAPField  a = f.getAnnotation(LDAPField.class);
    if (a == null)
    {
      throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_NOT_ANNOTATED.get(
           f.getName(), c.getName()));
    }

    final LDAPObject o = c.getAnnotation(LDAPObject.class);
    if (o == null)
    {
      throw new LDAPPersistException(ERR_FIELD_INFO_CLASS_NOT_ANNOTATED.get(
           c.getName()));
    }

    containingClass     = c;
    failOnInvalidValue  = a.failOnInvalidValue();
    includeInRDN        = a.inRDN();
    includeInAdd        = (includeInRDN || a.inAdd());
    includeInModify     = ((! includeInRDN) && a.inModify());
    filterUsage         = a.filterUsage();
    lazilyLoad          = a.lazilyLoad();
    isRequiredForDecode = (a.requiredForDecode() && (! lazilyLoad));
    isRequiredForEncode = (includeInRDN || a.requiredForEncode());
    defaultDecodeValues = a.defaultDecodeValue();
    defaultEncodeValues = a.defaultEncodeValue();

    if (lazilyLoad)
    {
      if (defaultDecodeValues.length > 0)
      {
        throw new LDAPPersistException(
             ERR_FIELD_INFO_LAZY_WITH_DEFAULT_DECODE.get(f.getName(),
                  c.getName()));
      }

      if (defaultEncodeValues.length > 0)
      {
        throw new LDAPPersistException(
             ERR_FIELD_INFO_LAZY_WITH_DEFAULT_ENCODE.get(f.getName(),
                  c.getName()));
      }

      if (includeInRDN)
      {
        throw new LDAPPersistException(ERR_FIELD_INFO_LAZY_IN_RDN.get(
             f.getName(), c.getName()));
      }
    }

    final int modifiers = f.getModifiers();
    if (Modifier.isFinal(modifiers))
    {
      throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_FINAL.get(
           f.getName(), c.getName()));
    }

    if (Modifier.isStatic(modifiers))
    {
      throw new LDAPPersistException(ERR_FIELD_INFO_FIELD_STATIC.get(
           f.getName(), c.getName()));
    }

    try
    {
      encoder = a.encoderClass().newInstance();
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      throw new LDAPPersistException(ERR_FIELD_INFO_CANNOT_GET_ENCODER.get(
           a.encoderClass().getName(), f.getName(), c.getName(),
           StaticUtils.getExceptionMessage(e)), e);
    }

    if (! encoder.supportsType(f.getGenericType()))
    {
      throw new LDAPPersistException(
           ERR_FIELD_INFO_ENCODER_UNSUPPORTED_TYPE.get(
                encoder.getClass().getName(), f.getName(), c.getName(),
                f.getGenericType()));
    }

    supportsMultipleValues = encoder.supportsMultipleValues(f);
    if (supportsMultipleValues)
    {
      failOnTooManyValues = false;
    }
    else
    {
      failOnTooManyValues = a.failOnTooManyValues();
      if (defaultDecodeValues.length > 1)
      {
        throw new LDAPPersistException(
             ERR_FIELD_INFO_UNSUPPORTED_MULTIPLE_DEFAULT_DECODE_VALUES.get(
                  f.getName(), c.getName()));
      }

      if (defaultEncodeValues.length > 1)
      {
        throw new LDAPPersistException(
             ERR_FIELD_INFO_UNSUPPORTED_MULTIPLE_DEFAULT_ENCODE_VALUES.get(
                  f.getName(), c.getName()));
      }
    }

    final String attrName = a.attribute();
    if ((attrName == null) || attrName.isEmpty())
    {
      attributeName = f.getName();
    }
    else
    {
      attributeName = attrName;
    }

    final StringBuilder invalidReason = new StringBuilder();
    if (! PersistUtils.isValidLDAPName(attributeName, true, invalidReason))
    {
      throw new LDAPPersistException(ERR_FIELD_INFO_INVALID_ATTR_NAME.get(
           f.getName(), c.getName(), invalidReason.toString()));
    }

    final String structuralClass;
    if (o.structuralClass().isEmpty())
    {
      structuralClass = StaticUtils.getUnqualifiedClassName(c);
    }
    else
    {
      structuralClass = o.structuralClass();
    }

    final String[] ocs = a.objectClass();
    if ((ocs == null) || (ocs.length == 0))
    {
      objectClasses = new String[] { structuralClass };
    }
    else
    {
      objectClasses = ocs;
    }

    for (final String s : objectClasses)
    {
      if (! s.equalsIgnoreCase(structuralClass))
      {
        boolean found = false;
        for (final String oc : o.auxiliaryClass())
        {
          if (s.equalsIgnoreCase(oc))
          {
            found = true;
            break;
          }
        }

        if (! found)
        {
          throw new LDAPPersistException(ERR_FIELD_INFO_INVALID_OC.get(
               f.getName(), c.getName(), s));
        }
      }
    }
  }



  /**
   * Retrieves the field with which this object is associated.
   *
   * @return  The field with which this object is associated.
   */
  @NotNull()
  public Field getField()
  {
    return field;
  }



  /**
   * Retrieves the class that is marked with the {@link LDAPObject} annotation
   * and contains the associated field.
   *
   * @return  The class that contains the associated field.
   */
  @NotNull()
  public Class getContainingClass()
  {
    return containingClass;
  }



  /**
   * Indicates whether attempts to initialize an object should fail if the LDAP
   * attribute has a value that cannot be stored in the associated field.
   *
   * @return  {@code true} if an exception should be thrown if an LDAP attribute
   *          has a value that cannot be assigned to the associated field, or
   *          {@code false} if the field should remain uninitialized.
   */
  public boolean failOnInvalidValue()
  {
    return failOnInvalidValue;
  }



  /**
   * Indicates whether attempts to initialize an object should fail if the
   * LDAP attribute has multiple values but the associated field can only hold a
   * single value.  Note that the value returned from this method may be
   * {@code false} even when the annotation has a value of {@code true} if the
   * associated field supports multiple values.
   *
   * @return  {@code true} if an exception should be thrown if an attribute has
   *          too many values to hold in the associated field, or {@code false}
   *          if the first value returned should be assigned to the field.
   */
  public boolean failOnTooManyValues()
  {
    return failOnTooManyValues;
  }



  /**
   * Indicates whether the associated field should be included in entries
   * generated for add operations.  Note that the value returned from this
   * method may be {@code true} even when the annotation has a value of
   * {@code false} if the associated field is to be included in entry RDNs.
   *
   * @return  {@code true} if the associated field should be included in entries
   *         generated for add operations, or {@code false} if not.
   */
  public boolean includeInAdd()
  {
    return includeInAdd;
  }



  /**
   * Indicates whether the associated field should be considered for inclusion
   * in the set of modifications generated for modify operations.  Note that the
   * value returned from this method may be {@code false} even when the
   * annotation has a value of {@code true} for the {@code inModify} element if
   * the associated field is to be included in entry RDNs.
   *
   * @return  {@code true} if the associated field should be considered for
   *          inclusion in the set of modifications generated for modify
   *          operations, or {@code false} if not.
   */
  public boolean includeInModify()
  {
    return includeInModify;
  }



  /**
   * Indicates whether the associated field should be used to generate entry
   * RDNs.
   *
   * @return  {@code true} if the associated field should be used to generate
   *          entry RDNs, or {@code false} if not.
   */
  public boolean includeInRDN()
  {
    return includeInRDN;
  }



  /**
   * Retrieves the filter usage for the associated field.
   *
   * @return  The filter usage for the associated field.
   */
  @NotNull()
  public FilterUsage getFilterUsage()
  {
    return filterUsage;
  }



  /**
   * Indicates whether the associated field should be considered required for
   * decode operations.
   *
   * @return  {@code true} if the associated field should be considered required
   *          for decode operations, or {@code false} if not.
   */
  public boolean isRequiredForDecode()
  {
    return isRequiredForDecode;
  }



  /**
   * Indicates whether the associated field should be considered required for
   * encode operations.  Note that the value returned from this method may be
   * {@code true} even when the annotation has a value of {@code true} for the
   * {@code requiredForEncode} element if the associated field is to be included
   * in entry RDNs.
   *
   * @return  {@code true} if the associated field should be considered required
   *          for encode operations, or {@code false} if not.
   */
  public boolean isRequiredForEncode()
  {
    return isRequiredForEncode;
  }



  /**
   * Indicates whether the associated field should be lazily-loaded.
   *
   * @return  {@code true} if the associated field should be lazily-loaded, or
   *          {@code false} if not.
   */
  public boolean lazilyLoad()
  {
    return lazilyLoad;
  }



  /**
   * Retrieves the encoder that should be used for the associated field.
   *
   * @return  The encoder that should be used for the associated field.
   */
  @NotNull()
  public ObjectEncoder getEncoder()
  {
    return encoder;
  }



  /**
   * Retrieves the name of the LDAP attribute used to hold values for the
   * associated field.
   *
   * @return  The name of the LDAP attribute used to hold values for the
   *          associated field.
   */
  @NotNull()
  public String getAttributeName()
  {
    return attributeName;
  }



  /**
   * Retrieves the set of default values that should be assigned to the
   * associated field if there are no values for the corresponding attribute in
   * the LDAP entry.
   *
   * @return  The set of default values for use when instantiating the object,
   *          or an empty array if no default values are defined.
   */
  @NotNull()
  public String[] getDefaultDecodeValues()
  {
    return defaultDecodeValues;
  }



  /**
   * Retrieves the set of default values that should be used when creating an
   * entry for an add operation if the associated field does not itself have any
   * values.
   *
   * @return  The set of default values for use in add operations, or an empty
   *          array if no default values are defined.
   */
  @NotNull()
  public String[] getDefaultEncodeValues()
  {
    return defaultEncodeValues;
  }



  /**
   * Retrieves the names of the object classes containing the associated
   * attribute.
   *
   * @return  The names of the object classes containing the associated
   *          attribute.
   */
  @NotNull()
  public String[] getObjectClasses()
  {
    return objectClasses;
  }



  /**
   * Indicates whether the associated field can hold multiple values.
   *
   * @return  {@code true} if the associated field can hold multiple values, or
   *          {@code false} if not.
   */
  public boolean supportsMultipleValues()
  {
    return supportsMultipleValues;
  }



  /**
   * Constructs a definition for an LDAP attribute type which may be added to
   * the directory server schema to allow it to hold the value of the associated
   * field.  Note that the object identifier used for the constructed attribute
   * type definition is not required to be valid or unique.
   *
   * @return  The constructed attribute type definition.
   *
   * @throws  LDAPPersistException  If the object encoder does not support
   *                                encoding values for the associated field
   *                                type.
   */
  @NotNull()
  AttributeTypeDefinition constructAttributeType()
       throws LDAPPersistException
  {
    return constructAttributeType(DefaultOIDAllocator.getInstance());
  }



  /**
   * Constructs a definition for an LDAP attribute type which may be added to
   * the directory server schema to allow it to hold the value of the associated
   * field.  Note that the object identifier used for the constructed attribute
   * type definition is not required to be valid or unique.
   *
   * @param  a  The OID allocator to use to generate the object identifier.  It
   *            must not be {@code null}.
   *
   * @return  The constructed attribute type definition.
   *
   * @throws  LDAPPersistException  If the object encoder does not support
   *                                encoding values for the associated field
   *                                type.
   */
  @NotNull()
  AttributeTypeDefinition constructAttributeType(@NotNull final OIDAllocator a)
       throws LDAPPersistException
  {
    return encoder.constructAttributeType(field, a);
  }



  /**
   * Encodes the value for the associated field from the provided object to an
   * attribute.
   *
   * @param  o                   The object containing the field to be encoded.
   * @param  ignoreRequiredFlag  Indicates whether to ignore the value of the
   *                             {@code requiredForEncode} setting.  If this is
   *                             {@code true}, then this method will always
   *                             return {@code null} if the field does not have
   *                             a value even if this field is marked as
   *                             required for encode processing.
   *
   * @return  The attribute containing the encoded representation of the field
   *          value if it is non-{@code null}, an encoded representation of the
   *          default add values if the associated field is {@code null} but
   *          default values are defined, or {@code null} if the associated
   *          field is {@code null} and there are no default values.
   *
   * @throws  LDAPPersistException  If a problem occurs while encoding the
   *                                value of the associated field for the
   *                                provided object, or if the field is marked
   *                                as required but is {@code null} and does not
   *                                have any default add values.
   */
  @Nullable()
  Attribute encode(@NotNull final Object o, final boolean ignoreRequiredFlag)
            throws LDAPPersistException
  {
    try
    {
      final Object fieldValue = field.get(o);
      if (fieldValue == null)
      {
        if (defaultEncodeValues.length > 0)
        {
          return new Attribute(attributeName, defaultEncodeValues);
        }

        if (isRequiredForEncode && (! ignoreRequiredFlag))
        {
          throw new LDAPPersistException(
               ERR_FIELD_INFO_MISSING_REQUIRED_VALUE.get(field.getName(),
                    containingClass.getName()));
        }

        return null;
      }

      return encoder.encodeFieldValue(field, fieldValue, attributeName);
    }
    catch (final LDAPPersistException lpe)
    {
      Debug.debugException(lpe);
      throw lpe;
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      throw new LDAPPersistException(
           ERR_FIELD_INFO_CANNOT_ENCODE.get(field.getName(),
                containingClass.getName(), StaticUtils.getExceptionMessage(e)),
           e);
    }
  }



  /**
   * Sets the value of the associated field in the given object from the
   * information contained in the provided attribute.
   *
   * @param  o               The object for which to update the associated
   *                         field.
   * @param  e               The entry being decoded.
   * @param  failureReasons  A list to which information about any failures
   *                         may be appended.
   *
   * @return  {@code true} if the decode process was completely successful, or
   *          {@code false} if there were one or more failures.
   */
  boolean decode(@NotNull final Object o, @NotNull final Entry e,
                 @NotNull final List failureReasons)
  {
    boolean successful = true;

    Attribute a = e.getAttribute(attributeName);
    if ((a == null) || (! a.hasValue()))
    {
      if (defaultDecodeValues.length > 0)
      {
        a = new Attribute(attributeName, defaultDecodeValues);
      }
      else
      {
        if (isRequiredForDecode)
        {
          successful = false;
          failureReasons.add(ERR_FIELD_INFO_MISSING_REQUIRED_ATTRIBUTE.get(
               containingClass.getName(), e.getDN(), attributeName,
               field.getName()));
        }

        try
        {
          encoder.setNull(field, o);
        }
        catch (final LDAPPersistException lpe)
        {
          Debug.debugException(lpe);
          successful = false;
          failureReasons.add(lpe.getMessage());
        }

        return successful;
      }
    }

    if (failOnTooManyValues && (a.size() > 1))
    {
      successful = false;
      failureReasons.add(ERR_FIELD_INFO_FIELD_NOT_MULTIVALUED.get(a.getName(),
           field.getName(), containingClass.getName()));
    }

    try
    {
      encoder.decodeField(field, o, a);
    }
    catch (final LDAPPersistException lpe)
    {
      Debug.debugException(lpe);
      if (failOnInvalidValue)
      {
        successful = false;
        failureReasons.add(lpe.getMessage());
      }
    }

    return successful;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy