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