View Javadoc
1   /*
2    * Copyright 2025 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.ldaptive;
18  
19  import static org.springframework.util.ObjectUtils.isEmpty;
20  
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.Optional;
27  import java.util.function.BiPredicate;
28  import java.util.stream.Stream;
29  import lombok.Getter;
30  import org.bremersee.ldaptive.transcoder.ValueTranscoderFactory;
31  import org.ldaptive.AttributeModification;
32  import org.ldaptive.AttributeModification.Type;
33  import org.ldaptive.LdapAttribute;
34  import org.ldaptive.LdapEntry;
35  import org.ldaptive.transcode.ValueTranscoder;
36  import org.springframework.util.Assert;
37  
38  /**
39   * The ldaptive attribute.
40   *
41   * @param <T> the type parameter
42   * @author Christian Bremer
43   */
44  public interface LdaptiveAttribute<T> {
45  
46    /**
47     * Gets name.
48     *
49     * @return the name
50     */
51    String getName();
52  
53    /**
54     * Is binary.
55     *
56     * @return the boolean
57     */
58    boolean isBinary();
59  
60    /**
61     * Gets value transcoder.
62     *
63     * @return the value transcoder
64     */
65    ValueTranscoder<T> getValueTranscoder();
66  
67    /**
68     * Determines whether the attribute exists in the given ldap entry or not.
69     *
70     * @param entry the entry
71     * @return {@code true} if the attribute exists, otherwise {@code false}
72     */
73    default boolean exists(LdapEntry entry) {
74      return !isEmpty(entry) && !isEmpty(entry.getAttribute(getName()));
75    }
76  
77    /**
78     * Gets value.
79     *
80     * @param entry the entry
81     * @return the value
82     */
83    default Optional<T> getValue(LdapEntry entry) {
84      return getValue(entry, null);
85    }
86  
87    /**
88     * Gets value.
89     *
90     * @param entry the entry
91     * @param defaultValue the default value
92     * @return the value
93     */
94    default Optional<T> getValue(LdapEntry entry, T defaultValue) {
95      return Optional.ofNullable(entry)
96          .map(e -> e.getAttribute(getName()))
97          .map(attr -> attr.getValue(getValueTranscoder().decoder()))
98          .or(() -> Optional.ofNullable(defaultValue));
99    }
100 
101   /**
102    * Gets values.
103    *
104    * @param entry the entry
105    * @return the values
106    */
107   default Stream<T> getValues(LdapEntry entry) {
108     return Stream.ofNullable(entry)
109         .map(e -> e.getAttribute(getName()))
110         .filter(Objects::nonNull)
111         .map(attr -> attr.getValues(getValueTranscoder().decoder()))
112         .filter(Objects::nonNull)
113         .flatMap(Collection::stream);
114   }
115 
116   /**
117    * Sets value.
118    *
119    * @param entry the entry
120    * @param value the value
121    * @return the value
122    */
123   default Optional<AttributeModification> setValue(LdapEntry entry, T value) {
124     return setValues(entry, isEmpty(value) ? List.of() : List.of(value));
125   }
126 
127   /**
128    * Sets value.
129    *
130    * @param entry the entry
131    * @param value the value
132    * @param condition the condition
133    * @return the value
134    */
135   default Optional<AttributeModification> setValue(
136       LdapEntry entry,
137       T value,
138       BiPredicate<T, T> condition) {
139     if (isEmpty(condition) || condition.test(getValue(entry).orElse(null), value)) {
140       return setValues(entry, isEmpty(value) ? List.of() : List.of(value));
141     }
142     return Optional.empty();
143   }
144 
145   /**
146    * Sets values.
147    *
148    * @param entry the entry
149    * @param values the values
150    * @return the values
151    */
152   default Optional<AttributeModification> setValues(LdapEntry entry, Collection<T> values) {
153     if (isEmpty(entry)) {
154       return Optional.empty();
155     }
156     if (isEmpty(values)) {
157       return remove(entry);
158     }
159     List<T> newValues = values.stream().filter(v -> !isEmpty(v)).toList();
160     if (isEmpty(entry.getAttribute(getName()))) {
161       return addValues(entry, newValues);
162     }
163     List<byte[]> newByteList = newValues.stream()
164         .map(v -> getValueTranscoder().encodeBinaryValue(v))
165         .toList();
166     Collection<byte[]> existingBytes = entry.getAttribute(getName()).getBinaryValues();
167     if (equals(existingBytes, newByteList)) {
168       return Optional.empty();
169     }
170     LdapAttribute attribute = createAttribute(newValues);
171     entry.addAttributes(attribute);
172     return Optional.of(new AttributeModification(Type.REPLACE, attribute));
173   }
174 
175   private boolean equals(Collection<byte[]> c1, Collection<byte[]> c2) {
176     if (c1.size() != c2.size()) {
177       return false;
178     }
179     Iterator<byte[]> iter1 = c1.iterator();
180     Iterator<byte[]> iter2 = c2.iterator();
181     while (iter1.hasNext() && iter2.hasNext()) {
182       if (!Arrays.equals(iter1.next(), iter2.next())) {
183         return false;
184       }
185     }
186     return true;
187   }
188 
189   private Optional<AttributeModification> addValues(LdapEntry entry, Collection<T> values) {
190     LdapAttribute attribute = createAttribute(values);
191     entry.addAttributes(attribute);
192     return Optional.of(new AttributeModification(Type.ADD, attribute));
193   }
194 
195   private Optional<AttributeModification> remove(LdapEntry entry) {
196     if (isEmpty(entry.getAttribute(getName()))) {
197       return Optional.empty();
198     }
199     entry.removeAttribute(getName());
200     return Optional.of(new AttributeModification(Type.DELETE, createAttribute()));
201   }
202 
203 
204   /**
205    * Create attribute ldap attribute.
206    *
207    * @return the ldap attribute
208    */
209   default LdapAttribute createAttribute() {
210     LdapAttribute attribute = new LdapAttribute(getName());
211     attribute.setBinary(isBinary());
212     return attribute;
213   }
214 
215   /**
216    * Create attribute ldap attribute.
217    *
218    * @param value the value
219    * @return the ldap attribute
220    */
221   default LdapAttribute createAttribute(T value) {
222     if (isEmpty(value)) {
223       return createAttribute();
224     }
225     return createAttribute(List.of(value));
226   }
227 
228   /**
229    * Create attribute ldap attribute.
230    *
231    * @param values the values
232    * @return the ldap attribute
233    */
234   default LdapAttribute createAttribute(Collection<T> values) {
235     if (isEmpty(values)) {
236       return createAttribute();
237     }
238     LdapAttribute attribute = new LdapAttribute(getName());
239     attribute.setBinary(isBinary());
240     if (!isEmpty(values)) {
241       attribute.addValues(getValueTranscoder().encoder(), values);
242     }
243     return attribute;
244   }
245 
246   /**
247    * Define ldaptive attribute.
248    *
249    * @param name the name
250    * @return the ldaptive attribute
251    */
252   static LdaptiveAttribute<String> define(String name) {
253     return define(name, false, ValueTranscoderFactory.getStringValueTranscoder());
254   }
255 
256   /**
257    * Define ldaptive attribute.
258    *
259    * @param <T> the type parameter
260    * @param name the name
261    * @param binary the binary
262    * @param valueTranscoder the value transcoder
263    * @return the ldaptive attribute
264    */
265   static <T> LdaptiveAttribute<T> define(
266       String name,
267       boolean binary,
268       ValueTranscoder<T> valueTranscoder) {
269     return new Specification<>(name, binary, valueTranscoder);
270   }
271 
272   /**
273    * The ldaptive attribute specification.
274    *
275    * @param <T> the type parameter
276    */
277   @SuppressWarnings("ClassCanBeRecord")
278   class Specification<T> implements LdaptiveAttribute<T> {
279 
280     @Getter
281     private final String name;
282 
283     @Getter
284     private final boolean binary;
285 
286     @Getter
287     private final ValueTranscoder<T> valueTranscoder;
288 
289     /**
290      * Instantiates a new specification.
291      *
292      * @param name the name
293      * @param binary the binary
294      * @param valueTranscoder the value transcoder
295      */
296     public Specification(String name, boolean binary, ValueTranscoder<T> valueTranscoder) {
297       Assert.hasText(name, "Name must not be null or empty.");
298       Assert.notNull(valueTranscoder, "ValueTranscoder must not be null.");
299       this.name = name;
300       this.binary = binary;
301       this.valueTranscoder = valueTranscoder;
302     }
303   }
304 
305 }