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