001    /*
002     * Copyright 2009-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2016 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     */
021    package com.unboundid.ldap.sdk.migrate.jndi;
022    
023    
024    
025    import java.util.Collection;
026    import javax.naming.NamingEnumeration;
027    import javax.naming.NamingException;
028    import javax.naming.directory.Attributes;
029    import javax.naming.directory.BasicAttribute;
030    import javax.naming.directory.BasicAttributes;
031    import javax.naming.directory.DirContext;
032    import javax.naming.directory.ModificationItem;
033    import javax.naming.directory.SearchResult;
034    import javax.naming.ldap.BasicControl;
035    import javax.naming.ldap.ExtendedResponse;
036    
037    import com.unboundid.asn1.ASN1Exception;
038    import com.unboundid.asn1.ASN1OctetString;
039    import com.unboundid.ldap.sdk.Attribute;
040    import com.unboundid.ldap.sdk.Control;
041    import com.unboundid.ldap.sdk.DN;
042    import com.unboundid.ldap.sdk.Entry;
043    import com.unboundid.ldap.sdk.ExtendedRequest;
044    import com.unboundid.ldap.sdk.ExtendedResult;
045    import com.unboundid.ldap.sdk.Modification;
046    import com.unboundid.ldap.sdk.ModificationType;
047    import com.unboundid.ldap.sdk.RDN;
048    import com.unboundid.util.Debug;
049    import com.unboundid.util.NotMutable;
050    import com.unboundid.util.ThreadSafety;
051    import com.unboundid.util.ThreadSafetyLevel;
052    
053    import 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)
064    public 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    }