001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2012-2015 ForgeRock AS. 016 */ 017 018package org.opends.admin.ads.util; 019 020import java.io.IOException; 021import java.net.ConnectException; 022import java.net.URI; 023import java.util.HashSet; 024import java.util.Hashtable; 025import java.util.Set; 026 027import javax.naming.CommunicationException; 028import javax.naming.Context; 029import javax.naming.NamingEnumeration; 030import javax.naming.NamingException; 031import javax.naming.directory.Attribute; 032import javax.naming.directory.Attributes; 033import javax.naming.directory.SearchControls; 034import javax.naming.directory.SearchResult; 035import javax.naming.ldap.Control; 036import javax.naming.ldap.InitialLdapContext; 037import javax.naming.ldap.StartTlsRequest; 038import javax.naming.ldap.StartTlsResponse; 039import javax.net.ssl.HostnameVerifier; 040import javax.net.ssl.KeyManager; 041import javax.net.ssl.TrustManager; 042 043import org.forgerock.i18n.LocalizableMessage; 044import org.forgerock.i18n.slf4j.LocalizedLogger; 045import org.opends.server.replication.plugin.EntryHistorical; 046import org.opends.server.schema.SchemaConstants; 047 048import com.forgerock.opendj.cli.Utils; 049 050/** 051 * Class providing some utilities to create LDAP connections using JNDI and 052 * to manage entries retrieved using JNDI. 053 * 054 */ 055public class ConnectionUtils 056{ 057 private static final String STARTTLS_PROPERTY = 058 "org.opends.connectionutils.isstarttls"; 059 060 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 061 062 /** 063 * Private constructor: this class cannot be instantiated. 064 */ 065 private ConnectionUtils() 066 { 067 } 068 069 /** 070 * Creates a clear LDAP connection and returns the corresponding LdapContext. 071 * This methods uses the specified parameters to create a JNDI environment 072 * hashtable and creates an InitialLdapContext instance. 073 * 074 * @param ldapURL 075 * the target LDAP URL 076 * @param dn 077 * passed as Context.SECURITY_PRINCIPAL if not null 078 * @param pwd 079 * passed as Context.SECURITY_CREDENTIALS if not null 080 * @param timeout 081 * passed as com.sun.jndi.ldap.connect.timeout if > 0 082 * @param env 083 * null or additional environment properties 084 * 085 * @throws NamingException 086 * the exception thrown when instantiating InitialLdapContext 087 * 088 * @return the created InitialLdapContext. 089 * @see javax.naming.Context 090 * @see javax.naming.ldap.InitialLdapContext 091 */ 092 public static InitialLdapContext createLdapContext(String ldapURL, String dn, 093 String pwd, int timeout, Hashtable<String, String> env) 094 throws NamingException 095 { 096 env = copy(env); 097 env.put(Context.INITIAL_CONTEXT_FACTORY, 098 "com.sun.jndi.ldap.LdapCtxFactory"); 099 env.put("java.naming.ldap.attributes.binary", 100 EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); 101 env.put(Context.PROVIDER_URL, ldapURL); 102 if (timeout >= 1) 103 { 104 env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(timeout)); 105 } 106 if (dn != null) 107 { 108 env.put(Context.SECURITY_PRINCIPAL, dn); 109 } 110 if (pwd != null) 111 { 112 env.put(Context.SECURITY_CREDENTIALS, pwd); 113 } 114 115 /* Contains the DirContext and the Exception if any */ 116 final Object[] pair = new Object[] 117 { null, null }; 118 final Hashtable<String, String> fEnv = env; 119 Thread t = new Thread(new Runnable() 120 { 121 @Override 122 public void run() 123 { 124 try 125 { 126 pair[0] = new InitialLdapContext(fEnv, null); 127 128 } catch (NamingException ne) 129 { 130 pair[1] = ne; 131 132 } catch (Throwable t) 133 { 134 t.printStackTrace(); 135 pair[1] = t; 136 } 137 } 138 }); 139 t.setDaemon(true); 140 return getInitialLdapContext(t, pair, timeout); 141 } 142 143 /** 144 * Creates an LDAPS connection and returns the corresponding LdapContext. 145 * This method uses the TrusteSocketFactory class so that the specified 146 * trust manager gets called during the SSL handshake. If trust manager is 147 * null, certificates are not verified during SSL handshake. 148 * 149 * @param ldapsURL the target *LDAPS* URL. 150 * @param dn passed as Context.SECURITY_PRINCIPAL if not null. 151 * @param pwd passed as Context.SECURITY_CREDENTIALS if not null. 152 * @param timeout passed as com.sun.jndi.ldap.connect.timeout if > 0. 153 * @param env null or additional environment properties. 154 * @param trustManager null or the trust manager to be invoked during SSL 155 * negotiation. 156 * @param keyManager null or the key manager to be invoked during SSL 157 * negotiation. 158 * @return the established connection with the given parameters. 159 * 160 * @throws NamingException the exception thrown when instantiating 161 * InitialLdapContext. 162 * 163 * @see javax.naming.Context 164 * @see javax.naming.ldap.InitialLdapContext 165 * @see TrustedSocketFactory 166 */ 167 public static InitialLdapContext createLdapsContext(String ldapsURL, 168 String dn, String pwd, int timeout, Hashtable<String, String> env, 169 TrustManager trustManager, KeyManager keyManager) throws NamingException { 170 env = copy(env); 171 env.put(Context.INITIAL_CONTEXT_FACTORY, 172 "com.sun.jndi.ldap.LdapCtxFactory"); 173 env.put("java.naming.ldap.attributes.binary", 174 EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); 175 env.put(Context.PROVIDER_URL, ldapsURL); 176 env.put("java.naming.ldap.factory.socket", 177 org.opends.admin.ads.util.TrustedSocketFactory.class.getName()); 178 179 if (dn != null) 180 { 181 env.put(Context.SECURITY_PRINCIPAL, dn); 182 } 183 184 if (pwd != null) 185 { 186 env.put(Context.SECURITY_CREDENTIALS, pwd); 187 } 188 189 if (trustManager == null) 190 { 191 trustManager = new BlindTrustManager(); 192 } 193 194 /* Contains the DirContext and the Exception if any */ 195 final Object[] pair = new Object[] {null, null}; 196 final Hashtable<String, String> fEnv = env; 197 final TrustManager fTrustManager = trustManager; 198 final KeyManager fKeyManager = keyManager; 199 200 Thread t = new Thread(new Runnable() { 201 @Override 202 public void run() { 203 try { 204 TrustedSocketFactory.setCurrentThreadTrustManager(fTrustManager, 205 fKeyManager); 206 pair[0] = new InitialLdapContext(fEnv, null); 207 } catch (NamingException | RuntimeException ne) { 208 pair[1] = ne; 209 } 210 } 211 }); 212 t.setDaemon(true); 213 return getInitialLdapContext(t, pair, timeout); 214 } 215 216 /** 217 * Clones the provided InitialLdapContext and returns a connection using 218 * the same parameters. 219 * @param ctx the connection to be cloned. 220 * @param timeout the timeout to establish the connection in milliseconds. 221 * Use {@code 0} to express no timeout. 222 * @param trustManager the trust manager to be used to connect. 223 * @param keyManager the key manager to be used to connect. 224 * @return the new InitialLdapContext connected to the server. 225 * @throws NamingException if there was an error creating the new connection. 226 */ 227 public static InitialLdapContext cloneInitialLdapContext( 228 final InitialLdapContext ctx, int timeout, TrustManager trustManager, 229 KeyManager keyManager) throws NamingException 230 { 231 Hashtable<?, ?> env = ctx.getEnvironment(); 232 Control[] ctls = ctx.getConnectControls(); 233 Control[] newCtls = null; 234 if (ctls != null) 235 { 236 newCtls = new Control[ctls.length]; 237 System.arraycopy(ctls, 0, newCtls, 0, ctls.length); 238 } 239 /* Contains the DirContext and the Exception if any */ 240 final Object[] pair = new Object[] {null, null}; 241 final Hashtable<?, ?> fEnv = env; 242 final TrustManager fTrustManager = trustManager; 243 final KeyManager fKeyManager = keyManager; 244 final Control[] fNewCtls = newCtls; 245 246 Thread t = new Thread(new Runnable() { 247 @Override 248 public void run() { 249 try { 250 if (isSSL(ctx) || isStartTLS(ctx)) 251 { 252 TrustedSocketFactory.setCurrentThreadTrustManager(fTrustManager, 253 fKeyManager); 254 } 255 pair[0] = new InitialLdapContext(fEnv, fNewCtls); 256 } catch (NamingException | RuntimeException ne) { 257 pair[1] = ne; 258 } 259 } 260 }); 261 return getInitialLdapContext(t, pair, timeout); 262 } 263 264 /** 265 * Creates an LDAP+StartTLS connection and returns the corresponding 266 * LdapContext. 267 * This method first creates an LdapContext with anonymous bind. Then it 268 * requests a StartTlsRequest extended operation. The StartTlsResponse is 269 * setup with the specified hostname verifier. Negotiation is done using a 270 * TrustSocketFactory so that the specified TrustManager gets called during 271 * the SSL handshake. 272 * If trust manager is null, certificates are not checked during SSL 273 * handshake. 274 * 275 * @param ldapURL the target *LDAP* URL. 276 * @param dn passed as Context.SECURITY_PRINCIPAL if not null. 277 * @param pwd passed as Context.SECURITY_CREDENTIALS if not null. 278 * @param timeout passed as com.sun.jndi.ldap.connect.timeout if > 0. 279 * @param env null or additional environment properties. 280 * @param trustManager null or the trust manager to be invoked during SSL 281 * negotiation. 282 * @param keyManager null or the key manager to be invoked during SSL 283 * negotiation. 284 * @param verifier null or the hostname verifier to be setup in the 285 * StartTlsResponse. 286 * @return the established connection with the given parameters. 287 * 288 * @throws NamingException the exception thrown when instantiating 289 * InitialLdapContext. 290 * 291 * @see javax.naming.Context 292 * @see javax.naming.ldap.InitialLdapContext 293 * @see javax.naming.ldap.StartTlsRequest 294 * @see javax.naming.ldap.StartTlsResponse 295 * @see TrustedSocketFactory 296 */ 297 298 public static InitialLdapContext createStartTLSContext(String ldapURL, 299 String dn, String pwd, int timeout, Hashtable<String, String> env, 300 TrustManager trustManager, KeyManager keyManager, 301 HostnameVerifier verifier) 302 throws NamingException 303 { 304 if (trustManager == null) 305 { 306 trustManager = new BlindTrustManager(); 307 } 308 if (verifier == null) { 309 verifier = new BlindHostnameVerifier(); 310 } 311 312 env = copy(env); 313 env.put(Context.INITIAL_CONTEXT_FACTORY, 314 "com.sun.jndi.ldap.LdapCtxFactory"); 315 env.put("java.naming.ldap.attributes.binary", 316 EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); 317 env.put(Context.PROVIDER_URL, ldapURL); 318 env.put(Context.SECURITY_AUTHENTICATION , "none"); 319 320 /* Contains the DirContext and the Exception if any */ 321 final Object[] pair = new Object[] {null, null}; 322 final Hashtable<?, ?> fEnv = env; 323 final String fDn = dn; 324 final String fPwd = pwd; 325 final TrustManager fTrustManager = trustManager; 326 final KeyManager fKeyManager = keyManager; 327 final HostnameVerifier fVerifier = verifier; 328 329 Thread t = new Thread(new Runnable() { 330 @Override 331 public void run() { 332 try { 333 StartTlsResponse tls; 334 335 InitialLdapContext result = new InitialLdapContext(fEnv, null); 336 337 tls = (StartTlsResponse) result.extendedOperation( 338 new StartTlsRequest()); 339 tls.setHostnameVerifier(fVerifier); 340 try 341 { 342 tls.negotiate(new TrustedSocketFactory(fTrustManager,fKeyManager)); 343 } 344 catch(IOException x) { 345 NamingException xx; 346 xx = new CommunicationException( 347 "Failed to negotiate Start TLS operation"); 348 xx.initCause(x); 349 result.close(); 350 throw xx; 351 } 352 353 result.addToEnvironment(STARTTLS_PROPERTY, "true"); 354 if (fDn != null) 355 { 356 result.addToEnvironment(Context.SECURITY_AUTHENTICATION , "simple"); 357 result.addToEnvironment(Context.SECURITY_PRINCIPAL, fDn); 358 if (fPwd != null) 359 { 360 result.addToEnvironment(Context.SECURITY_CREDENTIALS, fPwd); 361 } 362 result.reconnect(null); 363 } 364 pair[0] = result; 365 } catch (NamingException | RuntimeException ne) 366 { 367 pair[1] = ne; 368 } 369 } 370 }); 371 t.setDaemon(true); 372 return getInitialLdapContext(t, pair, timeout); 373 } 374 375 private static Hashtable<String, String> copy(Hashtable<String, String> env) { 376 return env != null ? new Hashtable<>(env) : new Hashtable<String, String>(); 377 } 378 379 /** 380 * Returns the LDAP URL used in the provided InitialLdapContext. 381 * @param ctx the context to analyze. 382 * @return the LDAP URL used in the provided InitialLdapContext. 383 */ 384 public static String getLdapUrl(InitialLdapContext ctx) 385 { 386 String s = null; 387 try 388 { 389 s = (String)ctx.getEnvironment().get(Context.PROVIDER_URL); 390 } 391 catch (NamingException ne) 392 { 393 // This is really strange. Seems like a bug somewhere. 394 logger.warn(LocalizableMessage.raw("Naming exception getting environment of "+ctx, 395 ne)); 396 } 397 return s; 398 } 399 400 /** 401 * Returns the host name used in the provided InitialLdapContext. 402 * @param ctx the context to analyze. 403 * @return the host name used in the provided InitialLdapContext. 404 */ 405 public static String getHostName(InitialLdapContext ctx) 406 { 407 String s = null; 408 try 409 { 410 URI ldapURL = new URI(getLdapUrl(ctx)); 411 s = ldapURL.getHost(); 412 } 413 catch (Throwable t) 414 { 415 // This is really strange. Seems like a bug somewhere. 416 logger.warn(LocalizableMessage.raw("Error getting host: "+t, t)); 417 } 418 return s; 419 } 420 421 /** 422 * Returns the port number used in the provided InitialLdapContext. 423 * @param ctx the context to analyze. 424 * @return the port number used in the provided InitialLdapContext. 425 */ 426 public static int getPort(InitialLdapContext ctx) 427 { 428 int port = -1; 429 try 430 { 431 URI ldapURL = new URI(getLdapUrl(ctx)); 432 port = ldapURL.getPort(); 433 } 434 catch (Throwable t) 435 { 436 // This is really strange. Seems like a bug somewhere. 437 logger.warn(LocalizableMessage.raw("Error getting port: "+t, t)); 438 } 439 return port; 440 } 441 442 /** 443 * Returns the host port representation of the server to which this 444 * context is connected. 445 * @param ctx the context to analyze. 446 * @return the host port representation of the server to which this 447 * context is connected. 448 */ 449 public static String getHostPort(InitialLdapContext ctx) 450 { 451 return getHostName(ctx)+":"+getPort(ctx); 452 } 453 454 /** 455 * Returns the bind DN used in the provided InitialLdapContext. 456 * @param ctx the context to analyze. 457 * @return the bind DN used in the provided InitialLdapContext. 458 */ 459 public static String getBindDN(InitialLdapContext ctx) 460 { 461 String bindDN = null; 462 try 463 { 464 bindDN = (String)ctx.getEnvironment().get(Context.SECURITY_PRINCIPAL); 465 } 466 catch (NamingException ne) 467 { 468 // This is really strange. Seems like a bug somewhere. 469 logger.warn(LocalizableMessage.raw("Naming exception getting environment of "+ctx, 470 ne)); 471 } 472 return bindDN; 473 } 474 475 /** 476 * Returns the password used in the provided InitialLdapContext. 477 * @param ctx the context to analyze. 478 * @return the password used in the provided InitialLdapContext. 479 */ 480 public static String getBindPassword(InitialLdapContext ctx) 481 { 482 String bindPwd = null; 483 try 484 { 485 bindPwd = (String)ctx.getEnvironment().get(Context.SECURITY_CREDENTIALS); 486 } 487 catch (NamingException ne) 488 { 489 // This is really strange. Seems like a bug somewhere. 490 logger.warn(LocalizableMessage.raw("Naming exception getting environment of "+ctx, 491 ne)); 492 } 493 return bindPwd; 494 } 495 496 /** 497 * Tells whether we are using SSL in the provided InitialLdapContext. 498 * @param ctx the context to analyze. 499 * @return <CODE>true</CODE> if we are using SSL and <CODE>false</CODE> 500 * otherwise. 501 */ 502 public static boolean isSSL(InitialLdapContext ctx) 503 { 504 boolean isSSL = false; 505 try 506 { 507 isSSL = getLdapUrl(ctx).toLowerCase().startsWith("ldaps"); 508 } 509 catch (Throwable t) 510 { 511 // This is really strange. Seems like a bug somewhere. 512 logger.warn(LocalizableMessage.raw("Error getting if is SSL "+t, t)); 513 } 514 return isSSL; 515 } 516 517 /** 518 * Tells whether we are using StartTLS in the provided InitialLdapContext. 519 * @param ctx the context to analyze. 520 * @return <CODE>true</CODE> if we are using StartTLS and <CODE>false</CODE> 521 * otherwise. 522 */ 523 public static boolean isStartTLS(InitialLdapContext ctx) 524 { 525 boolean isStartTLS = false; 526 try 527 { 528 isStartTLS = "true".equalsIgnoreCase((String)ctx.getEnvironment().get( 529 STARTTLS_PROPERTY)); 530 } 531 catch (NamingException ne) 532 { 533 // This is really strange. Seems like a bug somewhere. 534 logger.warn(LocalizableMessage.raw("Naming exception getting environment of "+ctx, 535 ne)); 536 } 537 return isStartTLS; 538 } 539 540 /** 541 * Method used to know if we can connect as administrator in a server with a 542 * given password and dn. 543 * @param ldapUrl the LDAP URL of the server. 544 * @param dn the dn to be used. 545 * @param pwd the password to be used. 546 * @param timeout the timeout to establish the connection in milliseconds. 547 * Use {@code 0} to express no timeout. 548 * @return <CODE>true</CODE> if we can connect and read the configuration and 549 * <CODE>false</CODE> otherwise. 550 */ 551 public static boolean canConnectAsAdministrativeUser(String ldapUrl, 552 String dn, String pwd, int timeout) 553 { 554 boolean canConnectAsAdministrativeUser = false; 555 try 556 { 557 InitialLdapContext ctx; 558 if (ldapUrl.toLowerCase().startsWith("ldap:")) 559 { 560 ctx = createLdapContext(ldapUrl, dn, pwd, timeout, 561 null); 562 } 563 else 564 { 565 ctx = createLdapsContext(ldapUrl, dn, pwd, timeout, 566 null, null, null); 567 } 568 569 canConnectAsAdministrativeUser = connectedAsAdministrativeUser(ctx); 570 } catch (NamingException ne) 571 { 572 // Nothing to do. 573 } catch (Throwable t) 574 { 575 throw new IllegalStateException("Unexpected throwable.", t); 576 } 577 return canConnectAsAdministrativeUser; 578 } 579 580 /** 581 * Method used to know if we are connected as administrator in a server with a 582 * given InitialLdapContext. 583 * @param ctx the context. 584 * @return <CODE>true</CODE> if we are connected and read the configuration 585 * and <CODE>false</CODE> otherwise. 586 */ 587 public static boolean connectedAsAdministrativeUser(InitialLdapContext ctx) 588 { 589 boolean connectedAsAdministrativeUser = false; 590 try 591 { 592 /* 593 * Search for the config to check that it is the directory manager. 594 */ 595 SearchControls searchControls = new SearchControls(); 596 searchControls.setSearchScope( 597 SearchControls. OBJECT_SCOPE); 598 searchControls.setReturningAttributes( 599 new String[] { SchemaConstants.NO_ATTRIBUTES }); 600 NamingEnumeration<SearchResult> sr = 601 ctx.search("cn=config", "objectclass=*", searchControls); 602 try 603 { 604 while (sr.hasMore()) 605 { 606 sr.next(); 607 } 608 } 609 finally 610 { 611 try 612 { 613 sr.close(); 614 } 615 catch(Exception ex) 616 { 617 logger.warn(LocalizableMessage.raw( 618 "Unexpected error closing enumeration on cn=Config entry", ex)); 619 } 620 } 621 connectedAsAdministrativeUser = true; 622 } catch (NamingException ne) 623 { 624 // Nothing to do. 625 } catch (Throwable t) 626 { 627 throw new IllegalStateException("Unexpected throwable.", t); 628 } 629 return connectedAsAdministrativeUser; 630 } 631 632 /** 633 * This is just a commodity method used to try to get an InitialLdapContext. 634 * @param t the Thread to be used to create the InitialLdapContext. 635 * @param pair an Object[] array that contains the InitialLdapContext and the 636 * Throwable if any occurred. 637 * @param timeout the timeout in milliseconds. If we do not get to create the 638 * connection before the timeout a CommunicationException will be thrown. 639 * @return the created InitialLdapContext 640 * @throws NamingException if something goes wrong during the creation. 641 */ 642 private static InitialLdapContext getInitialLdapContext(Thread t, 643 Object[] pair, int timeout) throws NamingException 644 { 645 try 646 { 647 if (timeout > 0) 648 { 649 t.start(); 650 t.join(timeout); 651 } else 652 { 653 t.run(); 654 } 655 656 } catch (InterruptedException x) 657 { 658 // This might happen for problems in sockets 659 // so it does not necessarily imply a bug 660 } 661 662 boolean throwException = false; 663 664 if (timeout > 0 && t.isAlive()) 665 { 666 t.interrupt(); 667 try 668 { 669 t.join(2000); 670 } catch (InterruptedException x) 671 { 672 // This might happen for problems in sockets 673 // so it does not necessarily imply a bug 674 } 675 throwException = true; 676 } 677 678 if (pair[0] == null && pair[1] == null) 679 { 680 throwException = true; 681 } 682 683 if (throwException) 684 { 685 NamingException xx; 686 ConnectException x = new ConnectException("Connection timed out"); 687 xx = new CommunicationException("Connection timed out"); 688 xx.initCause(x); 689 throw xx; 690 } 691 692 if (pair[1] != null) 693 { 694 if (pair[1] instanceof NamingException) 695 { 696 throw (NamingException) pair[1]; 697 698 } else if (pair[1] instanceof RuntimeException) 699 { 700 throw (RuntimeException) pair[1]; 701 702 } else if (pair[1] instanceof Throwable) 703 { 704 throw new IllegalStateException("Unexpected throwable occurred", 705 (Throwable) pair[1]); 706 } 707 } 708 return (InitialLdapContext) pair[0]; 709 } 710 711 /** 712 * Returns the LDAP URL for the provided parameters. 713 * @param host the host name. 714 * @param port the LDAP port. 715 * @param useSSL whether to use SSL or not. 716 * @return the LDAP URL for the provided parameters. 717 */ 718 public static String getLDAPUrl(String host, int port, boolean useSSL) 719 { 720 host = Utils.getHostNameForLdapUrl(host); 721 return (useSSL ? "ldaps://" : "ldap://") + host + ":" + port; 722 } 723 724 /** 725 * Returns the String representation of the first value of an attribute in a 726 * LDAP entry. 727 * @param entry the entry. 728 * @param attrName the attribute name. 729 * @return the String representation of the first value of an attribute in a 730 * LDAP entry. 731 * @throws NamingException if there is an error processing the entry. 732 */ 733 public static String getFirstValue(SearchResult entry, String attrName) 734 throws NamingException 735 { 736 String v = null; 737 Attributes attrs = entry.getAttributes(); 738 if (attrs != null) 739 { 740 Attribute attr = attrs.get(attrName); 741 if (attr != null && attr.size() > 0) 742 { 743 Object o = attr.get(); 744 if (o instanceof String) 745 { 746 v = (String)o; 747 } 748 else 749 { 750 v = String.valueOf(o); 751 } 752 } 753 } 754 return v; 755 } 756 757 /** 758 * Returns a Set with the String representation of the values of an attribute 759 * in a LDAP entry. The returned Set will never be null. 760 * @param entry the entry. 761 * @param attrName the attribute name. 762 * @return a Set with the String representation of the values of an attribute 763 * in a LDAP entry. 764 * @throws NamingException if there is an error processing the entry. 765 */ 766 public static Set<String> getValues(SearchResult entry, String attrName) 767 throws NamingException 768 { 769 Set<String> values = new HashSet<>(); 770 Attributes attrs = entry.getAttributes(); 771 if (attrs != null) 772 { 773 Attribute attr = attrs.get(attrName); 774 if (attr != null) 775 { 776 for (int i=0; i<attr.size(); i++) 777 { 778 values.add((String)attr.get(i)); 779 } 780 } 781 } 782 return values; 783 } 784}