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