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