LdaptiveEntryMapper.java

/*
 * Copyright 2019 the original author or authors.
 *
 * 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.
 */

package org.bremersee.data.ldaptive;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import org.ldaptive.AttributeModification;
import org.ldaptive.AttributeModification.Type;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.ldaptive.ModifyRequest;
import org.ldaptive.beans.LdapEntryMapper;
import org.ldaptive.transcode.ValueTranscoder;
import org.springframework.lang.Nullable;
import org.springframework.validation.annotation.Validated;

/**
 * The ldap entry mapper.
 *
 * @param <T> the type of the domain object
 * @author Christian Bremer
 */
@Validated
public interface LdaptiveEntryMapper<T> extends LdapEntryMapper<T> {

  /**
   * Get object classes of the ldap entry. The object classes are only required, if a new ldap entry should be
   * persisted.
   *
   * @return the object classes of the ldap entry
   */
  String[] getObjectClasses();

  @Override
  String mapDn(T domainObject);

  /**
   * Map a ldap entry into a domain object.
   *
   * @param ldapEntry the ldap entry
   * @return the domain object
   */
  @Nullable
  T map(@Nullable LdapEntry ldapEntry);

  @Override
  void map(LdapEntry source, T destination);

  @Override
  default void map(T source, LdapEntry destination) {
    mapAndComputeModifications(source, destination);
  }

  /**
   * Map and compute attribute modifications (see {@link LdapEntry#computeModifications(LdapEntry, LdapEntry)}**).
   *
   * @param source the source (domain object)
   * @param destination the destination (ldap entry)
   * @return the attribute modifications
   */
  AttributeModification[] mapAndComputeModifications(
      @NotNull T source,
      @NotNull LdapEntry destination);

  /**
   * Map and compute modify request.
   *
   * @param source the source (domain object)
   * @param destination the destination (ldap entry)
   * @return the modify request
   */
  default ModifyRequest mapAndComputeModifyRequest(
      @NotNull T source,
      @NotNull LdapEntry destination) {
    return new ModifyRequest(destination.getDn(), mapAndComputeModifications(source, destination));
  }

  /**
   * Gets attribute value.
   *
   * @param <T> the type parameter
   * @param ldapEntry the ldap entry
   * @param name the name
   * @param valueTranscoder the value transcoder
   * @param defaultValue the default value
   * @return the attribute value
   */
  static <T> T getAttributeValue(
      @Nullable final LdapEntry ldapEntry,
      @NotNull final String name,
      final ValueTranscoder<T> valueTranscoder,
      final T defaultValue) {
    final LdapAttribute attr = ldapEntry == null ? null : ldapEntry.getAttribute(name);
    final T value = attr != null ? attr.getValue(valueTranscoder.decoder()) : null;
    return value != null ? value : defaultValue;
  }

  /**
   * Gets attribute values.
   *
   * @param <T> the type parameter
   * @param ldapEntry the ldap entry
   * @param name the name
   * @param valueTranscoder the value transcoder
   * @return the attribute values
   */
  static <T> Collection<T> getAttributeValues(
      @Nullable final LdapEntry ldapEntry,
      @NotNull final String name,
      final ValueTranscoder<T> valueTranscoder) {
    final LdapAttribute attr = ldapEntry == null ? null : ldapEntry.getAttribute(name);
    final Collection<T> values = attr != null ? attr.getValues(valueTranscoder.decoder()) : null;
    return values != null ? values : new ArrayList<>();
  }

  /**
   * Gets attribute values as set.
   *
   * @param <T> the type parameter
   * @param ldapEntry the ldap entry
   * @param name the name
   * @param valueTranscoder the value transcoder
   * @return the attribute values as set
   */
  static <T> Set<T> getAttributeValuesAsSet(
      @Nullable final LdapEntry ldapEntry,
      @NotNull final String name,
      final ValueTranscoder<T> valueTranscoder) {
    return new LinkedHashSet<>(getAttributeValues(ldapEntry, name, valueTranscoder));
  }

  /**
   * Gets attribute values as list.
   *
   * @param <T> the type parameter
   * @param ldapEntry the ldap entry
   * @param name the name
   * @param valueTranscoder the value transcoder
   * @return the attribute values as list
   */
  static <T> List<T> getAttributeValuesAsList(
      @Nullable final LdapEntry ldapEntry,
      @NotNull final String name,
      final ValueTranscoder<T> valueTranscoder) {
    return new ArrayList<>(getAttributeValues(ldapEntry, name, valueTranscoder));
  }

  /**
   * Replaces the value of the attribute with the specified value.
   *
   * @param <T> the type of the domain object
   * @param ldapEntry the ldap entry
   * @param name the attribute name
   * @param value the attribute value
   * @param isBinary specifies whether the attribute value is binary or not
   * @param valueTranscoder the value transcoder (can be null if value is also null)
   * @param modifications the list of modifications
   */
  static <T> void setAttribute(
      @NotNull final LdapEntry ldapEntry,
      @NotNull final String name,
      @Nullable final T value,
      final boolean isBinary,
      final ValueTranscoder<T> valueTranscoder,
      @NotNull final List<AttributeModification> modifications) {

    setAttributes(
        ldapEntry,
        name,
        value != null ? Collections.singleton(value) : null,
        isBinary,
        valueTranscoder,
        modifications);
  }

  /**
   * Replaces the values of the attribute with the specified values.
   *
   * @param <T> the type of the domain object
   * @param ldapEntry the ldap entry
   * @param name the attribute name
   * @param values the values of the attribute
   * @param isBinary specifies whether the attribute value is binary or not
   * @param valueTranscoder the value transcoder (can be null if values is also null)
   * @param modifications the list of modifications
   */
  static <T> void setAttributes(
      @NotNull final LdapEntry ldapEntry,
      @NotNull final String name,
      @Nullable final Collection<T> values,
      final boolean isBinary,
      final ValueTranscoder<T> valueTranscoder,
      @NotNull final List<AttributeModification> modifications) {

    final Collection<T> realValues = values == null ? null : values.stream()
        .filter(value -> {
          if (value instanceof CharSequence) {
            return ((CharSequence) value).length() > 0;
          }
          return value != null;
        })
        .collect(Collectors.toList());
    LdapAttribute attr = ldapEntry.getAttribute(name);
    if (attr == null && realValues != null && !realValues.isEmpty()) {
      addAttributes(ldapEntry, name, realValues, isBinary, valueTranscoder, modifications);
    } else if (attr != null) {
      if (realValues == null || realValues.isEmpty()) {
        ldapEntry.removeAttribute(name);
        modifications.add(
            new AttributeModification(
                Type.DELETE,
                attr));
      } else if (!new ArrayList<>(realValues)
          .equals(new ArrayList<>(attr.getValues(valueTranscoder.decoder())))) {
        final LdapAttribute newAttr = new LdapAttribute();
        newAttr.setBinary(isBinary);
        newAttr.setName(name);
        newAttr.addValues(valueTranscoder.encoder(), realValues);
        ldapEntry.addAttributes(newAttr);
        modifications.add(
            new AttributeModification(
                Type.REPLACE,
                newAttr));
      }
    }
  }

  /**
   * Adds the specified value to the attribute with the specified name.
   *
   * @param <T> the type of the domain object
   * @param ldapEntry the ldap entry
   * @param name the attribute name
   * @param value the attribute value
   * @param isBinary specifies whether the attribute value is binary or not
   * @param valueTranscoder the value transcoder
   * @param modifications the list of modifications
   */
  static <T> void addAttribute(
      @NotNull final LdapEntry ldapEntry,
      @NotNull final String name,
      @Nullable final T value,
      final boolean isBinary,
      @NotNull final ValueTranscoder<T> valueTranscoder,
      @NotNull final List<AttributeModification> modifications) {
    addAttributes(
        ldapEntry,
        name,
        value != null ? Collections.singleton(value) : null,
        isBinary,
        valueTranscoder,
        modifications);
  }

  /**
   * Adds the specified values to the attribute with the specified name.
   *
   * @param <T> the type of the domain object
   * @param ldapEntry the ldap entry
   * @param name the attribute name
   * @param values the attribute values
   * @param isBinary specifies whether the attribute value is binary or not
   * @param valueTranscoder the value transcoder
   * @param modifications the list of modifications
   */
  static <T> void addAttributes(
      @NotNull final LdapEntry ldapEntry,
      @NotNull final String name,
      @Nullable final Collection<T> values,
      final boolean isBinary,
      @NotNull final ValueTranscoder<T> valueTranscoder,
      @NotNull final List<AttributeModification> modifications) {
    final Collection<T> realValues = values == null ? null : values.stream()
        .filter(value -> {
          if (value instanceof CharSequence) {
            return ((CharSequence) value).length() > 0;
          }
          return value != null;
        })
        .collect(Collectors.toList());
    if (realValues == null || realValues.isEmpty()) {
      return;
    }
    final LdapAttribute attr = ldapEntry.getAttribute(name);
    if (attr == null) {
      final LdapAttribute newAttr = new LdapAttribute();
      newAttr.setBinary(isBinary);
      newAttr.setName(name);
      newAttr.addValues(valueTranscoder.encoder(), realValues);
      ldapEntry.addAttributes(newAttr);
      modifications.add(
          new AttributeModification(
              Type.ADD,
              newAttr));
    } else {
      final List<T> newValues = new ArrayList<>(
          getAttributeValues(ldapEntry, name, valueTranscoder));
      newValues.addAll(realValues);
      setAttributes(ldapEntry, name, newValues, attr.isBinary(), valueTranscoder, modifications);
    }
  }

  /**
   * Removes an attribute with the specified name.
   *
   * @param ldapEntry the ldap entry
   * @param name the name
   * @param modifications the modifications
   */
  static void removeAttribute(
      @NotNull final LdapEntry ldapEntry,
      @NotNull final String name,
      @NotNull final List<AttributeModification> modifications) {
    final LdapAttribute attr = ldapEntry.getAttribute(name);
    if (attr == null) {
      return;
    }
    ldapEntry.removeAttributes(attr);
    modifications.add(
        new AttributeModification(
            Type.DELETE,
            attr));
  }

  /**
   * Removes an attribute with the specified value. If the value is {@code null}, the whole attribute will be removed.
   *
   * @param <T> the type of the domain object
   * @param ldapEntry the ldap entry
   * @param name the name
   * @param value the value
   * @param valueTranscoder the value transcoder
   * @param modifications the modifications
   */
  static <T> void removeAttribute(
      @NotNull final LdapEntry ldapEntry,
      @NotNull final String name,
      @Nullable final T value,
      final ValueTranscoder<T> valueTranscoder,
      @NotNull final List<AttributeModification> modifications) {
    LdapAttribute attr = ldapEntry.getAttribute(name);
    if (attr == null) {
      return;
    }
    if (value == null) {
      removeAttribute(ldapEntry, name, modifications);
    } else {
      removeAttributes(ldapEntry, name, Collections.singleton(value), valueTranscoder,
          modifications);
    }
  }

  /**
   * Remove attributes with the specified values. If values are empty or {@code null}, no attributes will be removed.
   *
   * @param <T> the type of the domain object
   * @param ldapEntry the ldap entry
   * @param name the name
   * @param values the values
   * @param valueTranscoder the value transcoder
   * @param modifications the modifications
   */
  static <T> void removeAttributes(
      @NotNull final LdapEntry ldapEntry,
      @NotNull final String name,
      @Nullable final Collection<T> values,
      final ValueTranscoder<T> valueTranscoder,
      @NotNull final List<AttributeModification> modifications) {

    final LdapAttribute attr = ldapEntry.getAttribute(name);
    if (attr == null || values == null || values.isEmpty()) {
      return;
    }
    final List<T> newValues = new ArrayList<>(getAttributeValues(ldapEntry, name, valueTranscoder));
    newValues.removeAll(values);
    setAttributes(ldapEntry, name, newValues, attr.isBinary(), valueTranscoder, modifications);
  }

  /**
   * Create dn string.
   *
   * @param rdn the rdn
   * @param rdnValue the rdn value
   * @param baseDn the base dn
   * @return the string
   */
  static String createDn(
      @NotNull final String rdn,
      @NotNull final String rdnValue,
      @NotNull final String baseDn) {
    return rdn + "=" + rdnValue + "," + baseDn;
  }

  /**
   * Gets rdn.
   *
   * @param dn the dn
   * @return the rdn
   */
  static String getRdn(final String dn) {
    if (dn == null) {
      return null;
    }
    int start = dn.indexOf('=');
    if (start < 0) {
      return dn;
    }
    int end = dn.indexOf(',', start);
    if (end < 0) {
      return dn.substring(start + 1).trim();
    }
    return dn.substring(start + 1, end).trim();
  }

}