LdaptiveAttribute.java
/*
* Copyright 2025 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.ldaptive;
import static org.springframework.util.ObjectUtils.isEmpty;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
import lombok.Getter;
import org.bremersee.ldaptive.transcoder.ValueTranscoderFactory;
import org.ldaptive.AttributeModification;
import org.ldaptive.AttributeModification.Type;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.ldaptive.transcode.ValueTranscoder;
import org.springframework.util.Assert;
/**
* The ldaptive attribute.
*
* @param <T> the type parameter
* @author Christian Bremer
*/
public interface LdaptiveAttribute<T> {
/**
* Gets name.
*
* @return the name
*/
String getName();
/**
* Is binary.
*
* @return the boolean
*/
boolean isBinary();
/**
* Gets value transcoder.
*
* @return the value transcoder
*/
ValueTranscoder<T> getValueTranscoder();
/**
* Determines whether the attribute exists in the given ldap entry or not.
*
* @param entry the entry
* @return {@code true} if the attribute exists, otherwise {@code false}
*/
default boolean exists(LdapEntry entry) {
return !isEmpty(entry) && !isEmpty(entry.getAttribute(getName()));
}
/**
* Gets value.
*
* @param entry the entry
* @return the value
*/
default Optional<T> getValue(LdapEntry entry) {
return getValue(entry, null);
}
/**
* Gets value.
*
* @param entry the entry
* @param defaultValue the default value
* @return the value
*/
default Optional<T> getValue(LdapEntry entry, T defaultValue) {
return Optional.ofNullable(entry)
.map(e -> e.getAttribute(getName()))
.map(attr -> attr.getValue(getValueTranscoder().decoder()))
.or(() -> Optional.ofNullable(defaultValue));
}
/**
* Gets values.
*
* @param entry the entry
* @return the values
*/
default Stream<T> getValues(LdapEntry entry) {
return Stream.ofNullable(entry)
.map(e -> e.getAttribute(getName()))
.filter(Objects::nonNull)
.map(attr -> attr.getValues(getValueTranscoder().decoder()))
.filter(Objects::nonNull)
.flatMap(Collection::stream);
}
/**
* Sets value.
*
* @param entry the entry
* @param value the value
* @return the value
*/
default Optional<AttributeModification> setValue(LdapEntry entry, T value) {
return setValues(entry, isEmpty(value) ? List.of() : List.of(value));
}
/**
* Sets value.
*
* @param entry the entry
* @param value the value
* @param condition the condition
* @return the value
*/
default Optional<AttributeModification> setValue(
LdapEntry entry,
T value,
BiPredicate<T, T> condition) {
if (isEmpty(condition) || condition.test(getValue(entry).orElse(null), value)) {
return setValues(entry, isEmpty(value) ? List.of() : List.of(value));
}
return Optional.empty();
}
/**
* Sets values.
*
* @param entry the entry
* @param values the values
* @return the values
*/
default Optional<AttributeModification> setValues(LdapEntry entry, Collection<T> values) {
if (isEmpty(entry)) {
return Optional.empty();
}
if (isEmpty(values)) {
return remove(entry);
}
List<T> newValues = values.stream().filter(v -> !isEmpty(v)).toList();
if (isEmpty(entry.getAttribute(getName()))) {
return addValues(entry, newValues);
}
List<byte[]> newByteList = newValues.stream()
.map(v -> getValueTranscoder().encodeBinaryValue(v))
.toList();
Collection<byte[]> existingBytes = entry.getAttribute(getName()).getBinaryValues();
if (equals(existingBytes, newByteList)) {
return Optional.empty();
}
LdapAttribute attribute = createAttribute(newValues);
entry.addAttributes(attribute);
return Optional.of(new AttributeModification(Type.REPLACE, attribute));
}
private boolean equals(Collection<byte[]> c1, Collection<byte[]> c2) {
if (c1.size() != c2.size()) {
return false;
}
Iterator<byte[]> iter1 = c1.iterator();
Iterator<byte[]> iter2 = c2.iterator();
while (iter1.hasNext() && iter2.hasNext()) {
if (!Arrays.equals(iter1.next(), iter2.next())) {
return false;
}
}
return true;
}
private Optional<AttributeModification> addValues(LdapEntry entry, Collection<T> values) {
LdapAttribute attribute = createAttribute(values);
entry.addAttributes(attribute);
return Optional.of(new AttributeModification(Type.ADD, attribute));
}
private Optional<AttributeModification> remove(LdapEntry entry) {
if (isEmpty(entry.getAttribute(getName()))) {
return Optional.empty();
}
entry.removeAttribute(getName());
return Optional.of(new AttributeModification(Type.DELETE, createAttribute()));
}
/**
* Create attribute ldap attribute.
*
* @return the ldap attribute
*/
default LdapAttribute createAttribute() {
LdapAttribute attribute = new LdapAttribute(getName());
attribute.setBinary(isBinary());
return attribute;
}
/**
* Create attribute ldap attribute.
*
* @param value the value
* @return the ldap attribute
*/
default LdapAttribute createAttribute(T value) {
if (isEmpty(value)) {
return createAttribute();
}
return createAttribute(List.of(value));
}
/**
* Create attribute ldap attribute.
*
* @param values the values
* @return the ldap attribute
*/
default LdapAttribute createAttribute(Collection<T> values) {
if (isEmpty(values)) {
return createAttribute();
}
LdapAttribute attribute = new LdapAttribute(getName());
attribute.setBinary(isBinary());
if (!isEmpty(values)) {
attribute.addValues(getValueTranscoder().encoder(), values);
}
return attribute;
}
/**
* Define ldaptive attribute.
*
* @param name the name
* @return the ldaptive attribute
*/
static LdaptiveAttribute<String> define(String name) {
return define(name, false, ValueTranscoderFactory.getStringValueTranscoder());
}
/**
* Define ldaptive attribute.
*
* @param <T> the type parameter
* @param name the name
* @param binary the binary
* @param valueTranscoder the value transcoder
* @return the ldaptive attribute
*/
static <T> LdaptiveAttribute<T> define(
String name,
boolean binary,
ValueTranscoder<T> valueTranscoder) {
return new Specification<>(name, binary, valueTranscoder);
}
/**
* The ldaptive attribute specification.
*
* @param <T> the type parameter
*/
@SuppressWarnings("ClassCanBeRecord")
class Specification<T> implements LdaptiveAttribute<T> {
@Getter
private final String name;
@Getter
private final boolean binary;
@Getter
private final ValueTranscoder<T> valueTranscoder;
/**
* Instantiates a new specification.
*
* @param name the name
* @param binary the binary
* @param valueTranscoder the value transcoder
*/
public Specification(String name, boolean binary, ValueTranscoder<T> valueTranscoder) {
Assert.hasText(name, "Name must not be null or empty.");
Assert.notNull(valueTranscoder, "ValueTranscoder must not be null.");
this.name = name;
this.binary = binary;
this.valueTranscoder = valueTranscoder;
}
}
}