View Javadoc
1   /*
2    * Copyright 2019 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 java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.LinkedHashSet;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.Optional;
27  import java.util.Set;
28  import java.util.stream.Stream;
29  import org.ldaptive.AttributeModification;
30  import org.ldaptive.AttributeModification.Type;
31  import org.ldaptive.LdapAttribute;
32  import org.ldaptive.LdapEntry;
33  import org.ldaptive.ModifyRequest;
34  import org.ldaptive.beans.LdapEntryMapper;
35  import org.ldaptive.dn.Dn;
36  import org.ldaptive.dn.NameValue;
37  import org.ldaptive.dn.RDn;
38  import org.ldaptive.transcode.ValueTranscoder;
39  
40  /**
41   * The ldap entry mapper.
42   *
43   * @param <T> the type of the domain object
44   * @author Christian Bremer
45   */
46  public interface LdaptiveEntryMapper<T> extends LdapEntryMapper<T> {
47  
48    /**
49     * Get object classes of the ldap entry. The object classes are only required, if a new ldap entry
50     * should be persisted.
51     *
52     * @return the object classes of the ldap entry
53     */
54    String[] getObjectClasses();
55  
56    /**
57     * Get mapped attribute names.
58     *
59     * @return the mapped attribute names
60     */
61    default String[] getMappedAttributeNames() {
62      return new String[0];
63    }
64  
65    /**
66     * Get binary attribute names.
67     *
68     * @return the binary attribute names
69     */
70    default String[] getBinaryAttributeNames() {
71      return new String[0];
72    }
73  
74    @Override
75    String mapDn(T domainObject);
76  
77    /**
78     * Map a ldap entry into a domain object.
79     *
80     * @param ldapEntry the ldap entry
81     * @return the domain object
82     */
83    T map(LdapEntry ldapEntry);
84  
85    @Override
86    void map(LdapEntry source, T destination);
87  
88    @Override
89    default void map(T source, LdapEntry destination) {
90      mapAndComputeModifications(source, destination);
91    }
92  
93    /**
94     * Map and compute attribute modifications (see
95     * {@link LdapEntry#computeModifications(LdapEntry, LdapEntry)}**).
96     *
97     * @param source the source (domain object); required
98     * @param destination the destination (ldap entry); required
99     * @return the attribute modifications
100    */
101   AttributeModification[] mapAndComputeModifications(
102       T source,
103       LdapEntry destination);
104 
105   /**
106    * Map and compute modify request.
107    *
108    * @param source the source (domain object); required
109    * @param destination the destination (ldap entry); required
110    * @return the modify request
111    */
112   default Optional<ModifyRequest> mapAndComputeModifyRequest(
113       T source,
114       LdapEntry destination) {
115 
116     return Optional.ofNullable(mapAndComputeModifications(source, destination))
117         .filter(mods -> mods.length > 0)
118         .map(mods -> new ModifyRequest(destination.getDn(), mods));
119   }
120 
121   /**
122    * Gets attribute value.
123    *
124    * @param <T> the type parameter
125    * @param ldapEntry the ldap entry; required
126    * @param name the name; required
127    * @param valueTranscoder the value transcoder; required
128    * @param defaultValue the default value
129    * @return the attribute value
130    */
131   static <T> T getAttributeValue(
132       LdapEntry ldapEntry,
133       String name,
134       ValueTranscoder<T> valueTranscoder,
135       T defaultValue) {
136     LdapAttribute attr = ldapEntry == null ? null : ldapEntry.getAttribute(name);
137     T value = attr != null ? attr.getValue(valueTranscoder.decoder()) : null;
138     return value != null ? value : defaultValue;
139   }
140 
141   /**
142    * Gets attribute value.
143    *
144    * @param <T> the type parameter
145    * @param ldapEntry the ldap entry
146    * @param attribute the attribute
147    * @param defaultValue the default value
148    * @return the attribute value
149    */
150   static <T> T getAttributeValue(
151       LdapEntry ldapEntry,
152       LdaptiveAttribute<T> attribute,
153       T defaultValue) {
154     return getAttributeValue(
155         ldapEntry,
156         attribute.getName(),
157         attribute.getValueTranscoder(),
158         defaultValue);
159   }
160 
161   /**
162    * Gets attribute values.
163    *
164    * @param <T> the type parameter
165    * @param ldapEntry the ldap entry; required
166    * @param name the name; required
167    * @param valueTranscoder the value transcoder; required
168    * @return the attribute values
169    */
170   static <T> Collection<T> getAttributeValues(
171       LdapEntry ldapEntry,
172       String name,
173       ValueTranscoder<T> valueTranscoder) {
174     LdapAttribute attr = ldapEntry == null ? null : ldapEntry.getAttribute(name);
175     Collection<T> values = attr != null ? attr.getValues(valueTranscoder.decoder()) : null;
176     return values != null ? values : new ArrayList<>();
177   }
178 
179   /**
180    * Gets attribute values.
181    *
182    * @param <T> the type parameter
183    * @param ldapEntry the ldap entry
184    * @param attribute the attribute
185    * @return the attribute values
186    */
187   static <T> Collection<T> getAttributeValues(
188       LdapEntry ldapEntry,
189       LdaptiveAttribute<T> attribute) {
190     return getAttributeValues(ldapEntry, attribute.getName(), attribute.getValueTranscoder());
191   }
192 
193   /**
194    * Gets attribute values as set.
195    *
196    * @param <T> the type parameter
197    * @param ldapEntry the ldap entry; required
198    * @param name the name; required
199    * @param valueTranscoder the value transcoder; required
200    * @return the attribute values as set
201    */
202   static <T> Set<T> getAttributeValuesAsSet(
203       LdapEntry ldapEntry,
204       String name,
205       ValueTranscoder<T> valueTranscoder) {
206     return new LinkedHashSet<>(getAttributeValues(ldapEntry, name, valueTranscoder));
207   }
208 
209   /**
210    * Gets attribute values as set.
211    *
212    * @param <T> the type parameter
213    * @param ldapEntry the ldap entry
214    * @param attribute the attribute
215    * @return the attribute values as set
216    */
217   static <T> Set<T> getAttributeValuesAsSet(
218       LdapEntry ldapEntry,
219       LdaptiveAttribute<T> attribute) {
220     return getAttributeValuesAsSet(ldapEntry, attribute.getName(), attribute.getValueTranscoder());
221   }
222 
223   /**
224    * Gets attribute values as list.
225    *
226    * @param <T> the type parameter
227    * @param ldapEntry the ldap entry; required
228    * @param name the name; required
229    * @param valueTranscoder the value transcoder; required
230    * @return the attribute values as list
231    */
232   static <T> List<T> getAttributeValuesAsList(
233       LdapEntry ldapEntry,
234       String name,
235       ValueTranscoder<T> valueTranscoder) {
236     return new ArrayList<>(getAttributeValues(ldapEntry, name, valueTranscoder));
237   }
238 
239   /**
240    * Gets attribute values as list.
241    *
242    * @param <T> the type parameter
243    * @param ldapEntry the ldap entry
244    * @param attribute the attribute
245    * @return the attribute values as list
246    */
247   static <T> List<T> getAttributeValuesAsList(
248       LdapEntry ldapEntry,
249       LdaptiveAttribute<T> attribute) {
250     return getAttributeValuesAsList(ldapEntry, attribute.getName(), attribute.getValueTranscoder());
251   }
252 
253   /**
254    * Replaces the value of the attribute with the specified value.
255    *
256    * @param <T> the type of the domain object
257    * @param ldapEntry the ldap entry; required
258    * @param name the attribute name; required
259    * @param value the attribute value; can be null
260    * @param isBinary specifies whether the attribute value is binary or not
261    * @param valueTranscoder the value transcoder (can be null if value is also null)
262    * @param modifications the list of modifications; required
263    */
264   static <T> void setAttribute(
265       LdapEntry ldapEntry,
266       String name,
267       T value,
268       boolean isBinary,
269       ValueTranscoder<T> valueTranscoder,
270       List<AttributeModification> modifications) {
271 
272     setAttributes(
273         ldapEntry,
274         name,
275         value != null ? Collections.singleton(value) : null,
276         isBinary,
277         valueTranscoder,
278         modifications);
279   }
280 
281   /**
282    * Sets attribute.
283    *
284    * @param <T> the type parameter
285    * @param ldapEntry the ldap entry
286    * @param attribute the attribute
287    * @param value the value
288    * @param modifications the modifications
289    */
290   static <T> void setAttribute(
291       LdapEntry ldapEntry,
292       LdaptiveAttribute<T> attribute,
293       T value,
294       List<AttributeModification> modifications) {
295 
296     setAttributes(
297         ldapEntry,
298         attribute.getName(),
299         value != null ? Collections.singleton(value) : null,
300         attribute.isBinary(),
301         attribute.getValueTranscoder(),
302         modifications);
303   }
304 
305   /**
306    * Replaces the values of the attribute with the specified values.
307    *
308    * @param <T> the type of the domain object
309    * @param ldapEntry the ldap entry; required
310    * @param name the attribute name; required
311    * @param values the values of the attribute
312    * @param isBinary specifies whether the attribute value is binary or not
313    * @param valueTranscoder the value transcoder (can be null if values is also null)
314    * @param modifications the list of modifications; required
315    */
316   static <T> void setAttributes(
317       LdapEntry ldapEntry,
318       String name,
319       Collection<T> values,
320       boolean isBinary,
321       ValueTranscoder<T> valueTranscoder,
322       List<AttributeModification> modifications) {
323 
324     Collection<T> realValues = Stream.ofNullable(values)
325         .flatMap(Collection::stream)
326         .filter(value -> {
327           if (value instanceof CharSequence cs) {
328             return !cs.isEmpty();
329           }
330           return value != null;
331         })
332         .toList();
333     LdapAttribute attr = ldapEntry.getAttribute(name);
334     if (attr == null && !realValues.isEmpty()) {
335       addAttributes(ldapEntry, name, realValues, isBinary, valueTranscoder, modifications);
336     } else if (attr != null) {
337       if (realValues.isEmpty()) {
338         ldapEntry.removeAttribute(name);
339         modifications.add(
340             new AttributeModification(
341                 Type.DELETE,
342                 attr));
343       } else if (areNotEqual(realValues, attr.getValues(valueTranscoder.decoder()))) {
344         LdapAttribute newAttr = new LdapAttribute();
345         newAttr.setBinary(isBinary);
346         newAttr.setName(name);
347         newAttr.addValues(valueTranscoder.encoder(), realValues);
348         ldapEntry.addAttributes(newAttr);
349         modifications.add(
350             new AttributeModification(
351                 Type.REPLACE,
352                 newAttr));
353       }
354     }
355   }
356 
357   /**
358    * Sets attributes.
359    *
360    * @param <T> the type parameter
361    * @param ldapEntry the ldap entry
362    * @param attribute the attribute
363    * @param values the values
364    * @param modifications the modifications
365    */
366   static <T> void setAttributes(
367       LdapEntry ldapEntry,
368       LdaptiveAttribute<T> attribute,
369       Collection<T> values,
370       List<AttributeModification> modifications) {
371     setAttributes(ldapEntry, attribute.getName(), values, attribute.isBinary(),
372         attribute.getValueTranscoder(), modifications);
373   }
374 
375   private static boolean areNotEqual(Collection<?> newValues, Collection<?> existingValues) {
376     if (newValues.size() != existingValues.size()) {
377       return true;
378     }
379     List<?> newValueList = new ArrayList<>(newValues);
380     List<?> existingValueList = new ArrayList<>(existingValues);
381     for (int i = 0; i < newValueList.size(); i++) {
382       Object newValue = newValueList.get(i);
383       Object existingValue = existingValueList.get(i);
384       if (newValue instanceof byte[] n && existingValue instanceof byte[] e) {
385         if (!Arrays.equals(n, e)) {
386           return true;
387         }
388       } else if (!Objects.equals(newValue, existingValue)) {
389         return true;
390       }
391     }
392     return false;
393   }
394 
395   /**
396    * Adds the specified value to the attribute with the specified name.
397    *
398    * @param <T> the type of the domain object
399    * @param ldapEntry the ldap entry; required
400    * @param name the attribute name; required
401    * @param value the attribute value; can be null
402    * @param isBinary specifies whether the attribute value is binary or not
403    * @param valueTranscoder the value transcoder; required
404    * @param modifications the list of modifications; required
405    */
406   static <T> void addAttribute(
407       LdapEntry ldapEntry,
408       String name,
409       T value,
410       boolean isBinary,
411       ValueTranscoder<T> valueTranscoder,
412       List<AttributeModification> modifications) {
413     addAttributes(
414         ldapEntry,
415         name,
416         value != null ? Collections.singleton(value) : null,
417         isBinary,
418         valueTranscoder,
419         modifications);
420   }
421 
422   /**
423    * Add attribute.
424    *
425    * @param <T> the type parameter
426    * @param ldapEntry the ldap entry
427    * @param attribute the attribute
428    * @param value the value
429    * @param modifications the modifications
430    */
431   static <T> void addAttribute(
432       LdapEntry ldapEntry,
433       LdaptiveAttribute<T> attribute,
434       T value,
435       List<AttributeModification> modifications) {
436     addAttributes(
437         ldapEntry,
438         attribute.getName(),
439         value != null ? Collections.singleton(value) : null,
440         attribute.isBinary(),
441         attribute.getValueTranscoder(),
442         modifications);
443   }
444 
445   /**
446    * Adds the specified values to the attribute with the specified name.
447    *
448    * @param <T> the type of the domain object
449    * @param ldapEntry the ldap entry; required
450    * @param name the attribute name; required
451    * @param values the attribute values; can be null
452    * @param isBinary specifies whether the attribute value is binary or not
453    * @param valueTranscoder the value transcoder; required
454    * @param modifications the list of modifications; required
455    */
456   static <T> void addAttributes(
457       LdapEntry ldapEntry,
458       String name,
459       Collection<T> values,
460       boolean isBinary,
461       ValueTranscoder<T> valueTranscoder,
462       List<AttributeModification> modifications) {
463     Collection<T> realValues = Stream.ofNullable(values)
464         .flatMap(Collection::stream)
465         .filter(value -> {
466           if (value instanceof CharSequence cs) {
467             return !cs.isEmpty();
468           }
469           return value != null;
470         })
471         .toList();
472     if (realValues.isEmpty()) {
473       return;
474     }
475     LdapAttribute attr = ldapEntry.getAttribute(name);
476     if (attr == null) {
477       LdapAttribute newAttr = new LdapAttribute();
478       newAttr.setBinary(isBinary);
479       newAttr.setName(name);
480       newAttr.addValues(valueTranscoder.encoder(), realValues);
481       ldapEntry.addAttributes(newAttr);
482       modifications.add(
483           new AttributeModification(
484               Type.ADD,
485               newAttr));
486     } else {
487       List<T> newValues = new ArrayList<>(
488           getAttributeValues(ldapEntry, name, valueTranscoder));
489       newValues.addAll(realValues);
490       setAttributes(ldapEntry, name, newValues, attr.isBinary(), valueTranscoder, modifications);
491     }
492   }
493 
494   /**
495    * Add attributes.
496    *
497    * @param <T> the type parameter
498    * @param ldapEntry the ldap entry
499    * @param attribute the attribute
500    * @param values the values
501    * @param modifications the modifications
502    */
503   static <T> void addAttributes(
504       LdapEntry ldapEntry,
505       LdaptiveAttribute<T> attribute,
506       Collection<T> values,
507       List<AttributeModification> modifications) {
508     addAttributes(ldapEntry, attribute.getName(), values, attribute.isBinary(),
509         attribute.getValueTranscoder(), modifications);
510   }
511 
512   /**
513    * Removes an attribute with the specified name.
514    *
515    * @param ldapEntry the ldap entry; required
516    * @param name the name; required
517    * @param modifications the modifications; required
518    */
519   static void removeAttribute(
520       LdapEntry ldapEntry,
521       String name,
522       List<AttributeModification> modifications) {
523     LdapAttribute attr = ldapEntry.getAttribute(name);
524     if (attr == null) {
525       return;
526     }
527     ldapEntry.removeAttributes(attr);
528     modifications.add(
529         new AttributeModification(
530             Type.DELETE,
531             attr));
532   }
533 
534   /**
535    * Removes an attribute with the specified value. If the value is {@code null}, the whole
536    * attribute will be removed.
537    *
538    * @param <T> the type of the domain object
539    * @param ldapEntry the ldap entry; required
540    * @param name the name; required
541    * @param value the value; can be null
542    * @param valueTranscoder the value transcoder; required
543    * @param modifications the modifications; required
544    */
545   static <T> void removeAttribute(
546       LdapEntry ldapEntry,
547       String name,
548       T value,
549       ValueTranscoder<T> valueTranscoder,
550       List<AttributeModification> modifications) {
551     LdapAttribute attr = ldapEntry.getAttribute(name);
552     if (attr == null) {
553       return;
554     }
555     if (value == null) {
556       removeAttribute(ldapEntry, name, modifications);
557     } else {
558       removeAttributes(ldapEntry, name, Collections.singleton(value), valueTranscoder,
559           modifications);
560     }
561   }
562 
563   /**
564    * Remove attribute.
565    *
566    * @param <T> the type parameter
567    * @param ldapEntry the ldap entry
568    * @param attribute the attribute
569    * @param value the value
570    * @param modifications the modifications
571    */
572   static <T> void removeAttribute(
573       LdapEntry ldapEntry,
574       LdaptiveAttribute<T> attribute,
575       T value,
576       List<AttributeModification> modifications) {
577     removeAttribute(ldapEntry, attribute.getName(), value, attribute.getValueTranscoder(),
578         modifications);
579   }
580 
581   /**
582    * Remove attributes with the specified values. If values are empty or {@code null}, no attributes
583    * will be removed.
584    *
585    * @param <T> the type of the domain object
586    * @param ldapEntry the ldap entry; required
587    * @param name the name; required
588    * @param values the values
589    * @param valueTranscoder the value transcoder; required
590    * @param modifications the modifications; required
591    */
592   static <T> void removeAttributes(
593       LdapEntry ldapEntry,
594       String name,
595       Collection<T> values,
596       ValueTranscoder<T> valueTranscoder,
597       List<AttributeModification> modifications) {
598 
599     LdapAttribute attr = ldapEntry.getAttribute(name);
600     if (attr == null || values == null || values.isEmpty()) {
601       return;
602     }
603     List<T> newValues = new ArrayList<>(getAttributeValues(ldapEntry, name, valueTranscoder));
604     newValues.removeAll(values);
605     setAttributes(ldapEntry, name, newValues, attr.isBinary(), valueTranscoder, modifications);
606   }
607 
608   /**
609    * Remove attributes.
610    *
611    * @param <T> the type parameter
612    * @param ldapEntry the ldap entry
613    * @param attribute the attribute
614    * @param values the values
615    * @param modifications the modifications
616    */
617   static <T> void removeAttributes(
618       LdapEntry ldapEntry,
619       LdaptiveAttribute<T> attribute,
620       Collection<T> values,
621       List<AttributeModification> modifications) {
622     removeAttributes(ldapEntry, attribute.getName(), values, attribute.getValueTranscoder(),
623         modifications);
624   }
625 
626   /**
627    * Create dn string.
628    *
629    * @param rdn the rdn; required
630    * @param rdnValue the rdn value; required
631    * @param baseDn the base dn; required
632    * @return the string
633    */
634   static String createDn(
635       String rdn,
636       String rdnValue,
637       String baseDn) {
638     return Dn.builder()
639         .add(new RDn(new NameValue(rdn, rdnValue)))
640         .add(new Dn(baseDn))
641         .build()
642         .format();
643   }
644 
645   /**
646    * Gets rdn value (not the rdn attribute name).
647    *
648    * @param dn the dn
649    * @return the rdn
650    */
651   static String getRdn(String dn) {
652     if (dn == null) {
653       return null;
654     }
655     try {
656       Dn parsedDn = new Dn(dn);
657       if (parsedDn.isEmpty()) {
658         return dn;
659       }
660       return parsedDn.getRDn().getNameValue().getStringValue();
661 
662     } catch (IllegalArgumentException ignored) {
663       return dn;
664     }
665   }
666 
667 }