001/*
002 * Copyright 2009-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-2022 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2009-2022 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.migrate.jndi;
037
038
039
040import java.util.Collection;
041import javax.naming.NamingEnumeration;
042import javax.naming.NamingException;
043import javax.naming.directory.Attributes;
044import javax.naming.directory.BasicAttribute;
045import javax.naming.directory.BasicAttributes;
046import javax.naming.directory.DirContext;
047import javax.naming.directory.ModificationItem;
048import javax.naming.directory.SearchResult;
049import javax.naming.ldap.BasicControl;
050import javax.naming.ldap.ExtendedResponse;
051
052import com.unboundid.asn1.ASN1Exception;
053import com.unboundid.asn1.ASN1OctetString;
054import com.unboundid.ldap.sdk.Attribute;
055import com.unboundid.ldap.sdk.Control;
056import com.unboundid.ldap.sdk.DN;
057import com.unboundid.ldap.sdk.Entry;
058import com.unboundid.ldap.sdk.ExtendedRequest;
059import com.unboundid.ldap.sdk.ExtendedResult;
060import com.unboundid.ldap.sdk.Modification;
061import com.unboundid.ldap.sdk.ModificationType;
062import com.unboundid.ldap.sdk.RDN;
063import com.unboundid.util.Debug;
064import com.unboundid.util.NotMutable;
065import com.unboundid.util.NotNull;
066import com.unboundid.util.Nullable;
067import com.unboundid.util.StaticUtils;
068import com.unboundid.util.ThreadSafety;
069import com.unboundid.util.ThreadSafetyLevel;
070
071
072
073/**
074 * This utility class provides a set of methods that may be used to convert
075 * between data structures in the Java Naming and Directory Interface (JNDI)
076 * and the corresponding data structures in the UnboundID LDAP SDK for Java.
077 */
078@NotMutable()
079@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
080public final class JNDIConverter
081{
082  /**
083   * An empty array of attributes.
084   */
085  @NotNull private static final Attribute[] NO_ATTRIBUTES = new Attribute[0];
086
087
088
089  /**
090   * An empty array of JNDI controls.
091   */
092  @NotNull private static final javax.naming.ldap.Control[] NO_JNDI_CONTROLS =
093       new javax.naming.ldap.Control[0];
094
095
096
097  /**
098   * An empty array of SDK modifications.
099   */
100  @NotNull private static final Modification[] NO_MODIFICATIONS =
101       new Modification[0];
102
103
104
105  /**
106   * An empty array of JNDI modification items.
107   */
108  @NotNull private static final ModificationItem[] NO_MODIFICATION_ITEMS =
109       new ModificationItem[0];
110
111
112
113  /**
114   * An empty array of SDK controls.
115   */
116  @NotNull private static final Control[] NO_SDK_CONTROLS = new Control[0];
117
118
119
120  /**
121   * Prevent this utility class from being instantiated.
122   */
123  private JNDIConverter()
124  {
125    // No implementation required.
126  }
127
128
129
130  /**
131   * Converts the provided JNDI attribute to an LDAP SDK attribute.
132   *
133   * @param  a  The attribute to be converted.
134   *
135   * @return  The LDAP SDK attribute that corresponds to the provided JNDI
136   *          attribute.
137   *
138   * @throws  NamingException  If a problem is encountered during the conversion
139   *                           process.
140   */
141  @Nullable()
142  public static Attribute convertAttribute(
143                     @Nullable final javax.naming.directory.Attribute a)
144         throws NamingException
145  {
146    if (a == null)
147    {
148      return null;
149    }
150
151    final String name = a.getID();
152    final ASN1OctetString[] values = new ASN1OctetString[a.size()];
153
154    for (int i=0; i < values.length; i++)
155    {
156      final Object value = a.get(i);
157      if (value instanceof byte[])
158      {
159        values[i] = new ASN1OctetString((byte[]) value);
160      }
161      else
162      {
163        values[i] = new ASN1OctetString(String.valueOf(value));
164      }
165    }
166
167    return new Attribute(name, values);
168  }
169
170
171
172  /**
173   * Converts the provided LDAP SDK attribute to a JNDI attribute.
174   *
175   * @param  a  The attribute to be converted.
176   *
177   * @return  The JNDI attribute that corresponds to the provided LDAP SDK
178   *          attribute.
179   */
180  @Nullable()
181  public static javax.naming.directory.Attribute convertAttribute(
182                     @Nullable final Attribute a)
183  {
184    if (a == null)
185    {
186      return null;
187    }
188
189    final BasicAttribute attr = new BasicAttribute(a.getName(), true);
190    for (final String v : a.getValues())
191    {
192      attr.add(v);
193    }
194
195    return attr;
196  }
197
198
199
200  /**
201   * Converts the provided JNDI attributes to an array of LDAP SDK attributes.
202   *
203   * @param  a  The attributes to be converted.
204   *
205   * @return  The array of LDAP SDK attributes that corresponds to the
206   *          provided JNDI attributes.
207   *
208   * @throws  NamingException  If a problem is encountered during the conversion
209   *                           process.
210   */
211  @NotNull()
212  public static Attribute[] convertAttributes(@Nullable final Attributes a)
213         throws NamingException
214  {
215    if (a == null)
216    {
217      return NO_ATTRIBUTES;
218    }
219
220    int i=0;
221    final Attribute[] attributes = new Attribute[a.size()];
222    final NamingEnumeration<? extends javax.naming.directory.Attribute> e =
223         a.getAll();
224
225    try
226    {
227      while (e.hasMoreElements())
228      {
229        attributes[i++] = convertAttribute(e.next());
230      }
231    }
232    finally
233    {
234      e.close();
235    }
236
237    return attributes;
238  }
239
240
241
242  /**
243   * Converts the provided array of LDAP SDK attributes to a set of JNDI
244   * attributes.
245   *
246   * @param  a  The array of attributes to be converted.
247   *
248   * @return  The JNDI attributes that corresponds to the provided LDAP SDK
249   *          attributes.
250   */
251  @NotNull()
252  public static Attributes convertAttributes(@Nullable final Attribute... a)
253  {
254    final BasicAttributes attrs = new BasicAttributes(true);
255    if (a == null)
256    {
257      return attrs;
258    }
259
260    for (final Attribute attr : a)
261    {
262      attrs.put(convertAttribute(attr));
263    }
264
265    return attrs;
266  }
267
268
269
270  /**
271   * Converts the provided collection of LDAP SDK attributes to a set of JNDI
272   * attributes.
273   *
274   * @param  a  The collection of attributes to be converted.
275   *
276   * @return  The JNDI attributes that corresponds to the provided LDAP SDK
277   *          attributes.
278   */
279  @NotNull()
280  public static Attributes convertAttributes(
281                                @Nullable final Collection<Attribute> a)
282  {
283    final BasicAttributes attrs = new BasicAttributes(true);
284    if (a == null)
285    {
286      return attrs;
287    }
288
289    for (final Attribute attr : a)
290    {
291      attrs.put(convertAttribute(attr));
292    }
293
294    return attrs;
295  }
296
297
298
299  /**
300   * Converts the provided JNDI control to an LDAP SDK control.
301   *
302   * @param  c  The control to be converted.
303   *
304   * @return  The LDAP SDK control that corresponds to the provided JNDI
305   *          control.
306   *
307   * @throws  NamingException  If a problem is encountered during the conversion
308   *                           process.
309   */
310  @Nullable
311  public static Control convertControl(
312                             @Nullable final javax.naming.ldap.Control c)
313         throws NamingException
314  {
315    if (c == null)
316    {
317      return null;
318    }
319
320    final ASN1OctetString value;
321    final byte[] valueBytes = c.getEncodedValue();
322    if ((valueBytes == null) || (valueBytes.length == 0))
323    {
324      value = null;
325    }
326    else
327    {
328      try
329      {
330        value = ASN1OctetString.decodeAsOctetString(valueBytes);
331      }
332      catch (final ASN1Exception ae)
333      {
334        throw new NamingException(StaticUtils.getExceptionMessage(ae));
335      }
336    }
337
338    return new Control(c.getID(), c.isCritical(), value);
339  }
340
341
342
343  /**
344   * Converts the provided LDAP SDK control to a JNDI control.
345   *
346   * @param  c  The control to be converted.
347   *
348   * @return  The JNDI control that corresponds to the provided LDAP SDK
349   *          control.
350   */
351  @Nullable()
352  public static javax.naming.ldap.Control convertControl(
353                                               @Nullable final Control c)
354  {
355    if (c == null)
356    {
357      return null;
358    }
359
360    final ASN1OctetString value = c.getValue();
361    if (value == null)
362    {
363      return new BasicControl(c.getOID(), c.isCritical(), null);
364    }
365    else
366    {
367      return new BasicControl(c.getOID(), c.isCritical(), value.encode());
368    }
369  }
370
371
372
373  /**
374   * Converts the provided array of JNDI controls to an array of LDAP SDK
375   * controls.
376   *
377   * @param  c  The array of JNDI controls to be converted.
378   *
379   * @return  The array of LDAP SDK controls that corresponds to the provided
380   *          array of JNDI controls.
381   *
382   * @throws  NamingException  If a problem is encountered during the conversion
383   *                           process.
384   */
385  @NotNull()
386  public static Control[] convertControls(
387                               @Nullable final javax.naming.ldap.Control... c)
388         throws NamingException
389  {
390    if (c == null)
391    {
392      return NO_SDK_CONTROLS;
393    }
394
395    final Control[] controls = new Control[c.length];
396    for (int i=0; i < controls.length; i++)
397    {
398      controls[i] = convertControl(c[i]);
399    }
400
401    return controls;
402  }
403
404
405
406  /**
407   * Converts the provided array of LDAP SDK controls to an array of JNDI
408   * controls.
409   *
410   * @param  c  The array of LDAP SDK controls to be converted.
411   *
412   * @return  The array of JNDI controls that corresponds to the provided array
413   *          of LDAP SDK controls.
414   */
415  @NotNull()
416  public static javax.naming.ldap.Control[] convertControls(
417                                                 @Nullable final Control... c)
418  {
419    if (c == null)
420    {
421      return NO_JNDI_CONTROLS;
422    }
423
424    final javax.naming.ldap.Control[] controls =
425         new javax.naming.ldap.Control[c.length];
426    for (int i=0; i < controls.length; i++)
427    {
428      controls[i] = convertControl(c[i]);
429    }
430
431    return controls;
432  }
433
434
435
436  /**
437   * Converts the provided JNDI extended request to an LDAP SDK extended
438   * request.
439   *
440   * @param  r  The request to be converted.
441   *
442   * @return  The LDAP SDK extended request that corresponds to the provided
443   *          JNDI extended request.
444   *
445   * @throws  NamingException  If a problem is encountered during the conversion
446   *                           process.
447   */
448  @Nullable()
449  public static ExtendedRequest convertExtendedRequest(
450                     @Nullable final javax.naming.ldap.ExtendedRequest r)
451         throws NamingException
452  {
453    if (r == null)
454    {
455      return null;
456    }
457
458    return JNDIExtendedRequest.toSDKExtendedRequest(r);
459  }
460
461
462
463  /**
464   * Converts the provided LDAP SDK extended request to a JNDI extended request.
465   *
466   * @param  r  The request to be converted.
467   *
468   * @return  The JNDI extended request that corresponds to the provided LDAP
469   *          SDK extended request.
470   */
471  @Nullable()
472  public static javax.naming.ldap.ExtendedRequest convertExtendedRequest(
473                     @Nullable final ExtendedRequest r)
474  {
475    if (r == null)
476    {
477      return null;
478    }
479
480    return new JNDIExtendedRequest(r);
481  }
482
483
484
485  /**
486   * Converts the provided JNDI extended response to an LDAP SDK extended
487   * result.
488   *
489   * @param  r  The response to be converted.
490   *
491   * @return  The LDAP SDK extended result that corresponds to the provided
492   *          JNDI extended response.
493   *
494   * @throws  NamingException  If a problem is encountered during the conversion
495   *                           process.
496   */
497  @Nullable()
498  public static ExtendedResult convertExtendedResponse(
499                                    @Nullable final ExtendedResponse r)
500         throws NamingException
501  {
502    if (r == null)
503    {
504      return null;
505    }
506
507    return JNDIExtendedResponse.toSDKExtendedResult(r);
508  }
509
510
511
512  /**
513   * Converts the provided LDAP SDK extended result to a JNDI extended response.
514   *
515   * @param  r  The result to be converted.
516   *
517   * @return  The JNDI extended response that corresponds to the provided LDAP
518   *          SDK extended result.
519   */
520  @Nullable()
521  public static ExtendedResponse convertExtendedResult(
522                                      @Nullable final ExtendedResult r)
523  {
524    if (r == null)
525    {
526      return null;
527    }
528
529    return new JNDIExtendedResponse(r);
530  }
531
532
533
534  /**
535   * Converts the provided JNDI modification item to an LDAP SDK modification.
536   *
537   * @param  m  The JNDI modification item to be converted.
538   *
539   * @return  The LDAP SDK modification that corresponds to the provided JNDI
540   *          modification item.
541   *
542   * @throws  NamingException  If a problem is encountered during the conversion
543   *                           process.
544   */
545  @Nullable()
546  public static Modification convertModification(
547                                  @Nullable final ModificationItem m)
548         throws NamingException
549  {
550    if (m == null)
551    {
552      return null;
553    }
554
555    final ModificationType modType;
556    switch (m.getModificationOp())
557    {
558      case DirContext.ADD_ATTRIBUTE:
559        modType = ModificationType.ADD;
560        break;
561      case DirContext.REMOVE_ATTRIBUTE:
562        modType = ModificationType.DELETE;
563        break;
564      case DirContext.REPLACE_ATTRIBUTE:
565        modType = ModificationType.REPLACE;
566        break;
567      default:
568        throw new NamingException("Unsupported modification type " + m);
569    }
570
571    final Attribute a = convertAttribute(m.getAttribute());
572
573    return new Modification(modType, a.getName(), a.getRawValues());
574  }
575
576
577
578  /**
579   * Converts the provided LDAP SDK modification to a JNDI modification item.
580   *
581   * @param  m  The LDAP SDK modification to be converted.
582   *
583   * @return  The JNDI modification item that corresponds to the provided LDAP
584   *          SDK modification.
585   *
586   * @throws  NamingException  If a problem is encountered during the conversion
587   *                           process.
588   */
589  @Nullable()
590  public static ModificationItem convertModification(
591                                      @Nullable final Modification m)
592         throws NamingException
593  {
594    if (m == null)
595    {
596      return null;
597    }
598
599    final int modType;
600    switch (m.getModificationType().intValue())
601    {
602      case ModificationType.ADD_INT_VALUE:
603        modType = DirContext.ADD_ATTRIBUTE;
604        break;
605      case ModificationType.DELETE_INT_VALUE:
606        modType = DirContext.REMOVE_ATTRIBUTE;
607        break;
608      case ModificationType.REPLACE_INT_VALUE:
609        modType = DirContext.REPLACE_ATTRIBUTE;
610        break;
611      default:
612        throw new NamingException("Unsupported modification type " + m);
613    }
614
615    return new ModificationItem(modType, convertAttribute(m.getAttribute()));
616  }
617
618
619
620  /**
621   * Converts the provided array of JNDI modification items to an array of LDAP
622   * SDK modifications.
623   *
624   * @param  m  The array of JNDI modification items to be converted.
625   *
626   * @return  The array of LDAP SDK modifications that corresponds to the
627   *          provided array of JNDI modification items.
628   *
629   * @throws  NamingException  If a problem is encountered during the conversion
630   *                           process.
631   */
632  @NotNull()
633  public static Modification[] convertModifications(
634                                    @Nullable final ModificationItem... m)
635         throws NamingException
636  {
637    if (m == null)
638    {
639      return NO_MODIFICATIONS;
640    }
641
642    final Modification[] mods = new Modification[m.length];
643    for (int i=0; i < m.length; i++)
644    {
645      mods[i] = convertModification(m[i]);
646    }
647
648    return mods;
649  }
650
651
652
653  /**
654   * Converts the provided array of LDAP SDK modifications to an array of JNDI
655   * modification items.
656   *
657   * @param  m  The array of LDAP SDK modifications to be converted.
658   *
659   * @return  The array of JNDI modification items that corresponds to the
660   *          provided array of LDAP SDK modifications.
661   *
662   * @throws  NamingException  If a problem is encountered during the conversion
663   *                           process.
664   */
665  @NotNull()
666  public static ModificationItem[] convertModifications(
667                                        @Nullable final Modification... m)
668         throws NamingException
669  {
670    if (m == null)
671    {
672      return NO_MODIFICATION_ITEMS;
673    }
674
675    final ModificationItem[] mods = new ModificationItem[m.length];
676    for (int i=0; i < m.length; i++)
677    {
678      mods[i] = convertModification(m[i]);
679    }
680
681    return mods;
682  }
683
684
685
686  /**
687   * Converts the provided JNDI search result object to an LDAP SDK entry.
688   *
689   * @param  r  The JNDI search result object to be converted.
690   *
691   * @return  The LDAP SDK entry that corresponds to the provided JNDI search
692   *          result.
693   *
694   * @throws  NamingException  If a problem is encountered during the conversion
695   *                           process.
696   */
697  @Nullable()
698  public static Entry convertSearchEntry(@Nullable final SearchResult r)
699         throws NamingException
700  {
701    return convertSearchEntry(r, null);
702  }
703
704
705
706  /**
707   * Converts the provided JNDI search result object to an LDAP SDK entry.
708   *
709   * @param  r              The JNDI search result object to be converted.
710   * @param  contextBaseDN  The base DN for the JNDI context over which the
711   *                        search result was retrieved.  If it is
712   *                        non-{@code null} and non-empty, then it will be
713   *                        appended to the result of the {@code getName} method
714   *                        to obtain the entry's full DN.
715   *
716   * @return  The LDAP SDK entry that corresponds to the provided JNDI search
717   *          result.
718   *
719   * @throws  NamingException  If a problem is encountered during the conversion
720   *                           process.
721   */
722  @Nullable
723  public static Entry convertSearchEntry(@Nullable final SearchResult r,
724                                         @Nullable final String contextBaseDN)
725         throws NamingException
726  {
727    if (r == null)
728    {
729      return null;
730    }
731
732    final String dn;
733    if ((contextBaseDN == null) || contextBaseDN.isEmpty())
734    {
735      dn = r.getName();
736    }
737    else
738    {
739      final String name = r.getName();
740      if ((name == null) || name.isEmpty())
741      {
742        dn = contextBaseDN;
743      }
744      else
745      {
746        dn = r.getName() + ',' + contextBaseDN;
747      }
748    }
749
750    return new Entry(dn, convertAttributes(r.getAttributes()));
751  }
752
753
754
755  /**
756   * Converts the provided LDAP SDK entry to a JNDI search result.
757   *
758   * @param  e  The entry to be converted to a JNDI search result.
759   *
760   * @return  The JNDI search result that corresponds to the provided LDAP SDK
761   *          entry.
762   */
763  @Nullable()
764  public static SearchResult convertSearchEntry(@Nullable final Entry e)
765  {
766    return convertSearchEntry(e, null);
767  }
768
769
770
771  /**
772   * Converts the provided LDAP SDK entry to a JNDI search result.
773   *
774   * @param  e              The entry to be converted to a JNDI search result.
775   * @param  contextBaseDN  The base DN for the JNDI context over which the
776   *                        search result was retrieved.  If it is
777   *                        non-{@code null} and non-empty, then it will be
778   *                        removed from the end of the entry's DN in order to
779   *                        obtain the name for the {@code SearchResult} that is
780   *                        returned.
781   *
782   * @return  The JNDI search result that corresponds to the provided LDAP SDK
783   *          entry.
784   */
785  @Nullable()
786  public static SearchResult convertSearchEntry(@Nullable final Entry e,
787                                  @Nullable final String contextBaseDN)
788  {
789    if (e == null)
790    {
791      return null;
792    }
793
794    String name = e.getDN();
795    if ((contextBaseDN != null) && (! contextBaseDN.isEmpty()))
796    {
797      try
798      {
799        final DN parsedEntryDN = e.getParsedDN();
800        final DN parsedBaseDN = new DN(contextBaseDN);
801        if (parsedEntryDN.equals(parsedBaseDN))
802        {
803          name = "";
804        }
805        else if (parsedEntryDN.isDescendantOf(parsedBaseDN, false))
806        {
807          final RDN[] entryRDNs = parsedEntryDN.getRDNs();
808          final RDN[] baseRDNs = parsedBaseDN.getRDNs();
809          final RDN[] remainingRDNs =
810               new RDN[entryRDNs.length - baseRDNs.length];
811          System.arraycopy(entryRDNs, 0, remainingRDNs, 0,
812               remainingRDNs.length);
813          name = new DN(remainingRDNs).toString();
814        }
815      }
816      catch (final Exception ex)
817      {
818        Debug.debugException(ex);
819      }
820    }
821
822    final Collection<Attribute> attrs = e.getAttributes();
823    final Attribute[] attributes = new Attribute[attrs.size()];
824    attrs.toArray(attributes);
825
826    return new SearchResult(name, null, convertAttributes(attributes));
827  }
828}