1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
40
41
42
43
44 public interface LdaptiveAttribute<T> {
45
46
47
48
49
50
51 String getName();
52
53
54
55
56
57
58 boolean isBinary();
59
60
61
62
63
64
65 ValueTranscoder<T> getValueTranscoder();
66
67
68
69
70
71
72
73 default boolean exists(LdapEntry entry) {
74 return !isEmpty(entry) && !isEmpty(entry.getAttribute(getName()));
75 }
76
77
78
79
80
81
82
83 default Optional<T> getValue(LdapEntry entry) {
84 return getValue(entry, null);
85 }
86
87
88
89
90
91
92
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
103
104
105
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
118
119
120
121
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
129
130
131
132
133
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
147
148
149
150
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
206
207
208
209 default LdapAttribute createAttribute() {
210 LdapAttribute attribute = new LdapAttribute(getName());
211 attribute.setBinary(isBinary());
212 return attribute;
213 }
214
215
216
217
218
219
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
230
231
232
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
248
249
250
251
252 static LdaptiveAttribute<String> define(String name) {
253 return define(name, false, ValueTranscoderFactory.getStringValueTranscoder());
254 }
255
256
257
258
259
260
261
262
263
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
274
275
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
291
292
293
294
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 }