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 2014 ForgeRock AS. 015 */ 016package org.forgerock.openig.ldap; 017 018import static org.forgerock.opendj.ldap.Connections.*; 019 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.concurrent.ConcurrentHashMap; 024 025import org.forgerock.opendj.ldap.ConnectionFactory; 026import org.forgerock.opendj.ldap.DN; 027import org.forgerock.opendj.ldap.ErrorResultException; 028import org.forgerock.opendj.ldap.Filter; 029import org.forgerock.opendj.ldap.LDAPConnectionFactory; 030import org.forgerock.opendj.ldap.LDAPOptions; 031import org.forgerock.opendj.ldap.SearchScope; 032 033/** 034 * This class acts as a simplified access point into the OpenDJ LDAP SDK. Whilst 035 * it is possible for scripts to access the OpenDJ LDAP SDK APIs directly, this 036 * class simplifies the most common use cases by exposes fields and methods for: 037 * <ul> 038 * <li>creating and caching LDAP connections 039 * <li>parsing DNs and LDAP filters 040 * <li>simple access to LDAP scopes. 041 * </ul> 042 */ 043public final class LdapClient { 044 private static final LdapClient INSTANCE = new LdapClient(); 045 046 /** 047 * Returns an instance of an {@code LdapClient}. 048 * 049 * @return An instance of an {@code LdapClient}. 050 */ 051 public static LdapClient getInstance() { 052 return INSTANCE; 053 } 054 055 private final ConcurrentHashMap<String, ConnectionFactory> factories = 056 new ConcurrentHashMap<String, ConnectionFactory>(); 057 058 /** 059 * A map containing the LDAP scopes making it easier to specify scopes 060 * within scripts where Maps are exposed as properties, e.g. in Groovy the 061 * sub-tree scope may be specified using the value "ldap.scope.sub". 062 */ 063 private final Map<String, SearchScope> scope; 064 065 private LdapClient() { 066 final Map<String, SearchScope> map = new HashMap<String, SearchScope>(4); 067 for (final SearchScope scope : SearchScope.values()) { 068 map.put(scope.toString(), scope); 069 } 070 scope = Collections.unmodifiableMap(map); 071 } 072 073 /** 074 * Returns the {@link SearchScope} available. 075 * @return the {@link SearchScope} available. 076 */ 077 public Map<String, SearchScope> getScope() { 078 return scope; 079 } 080 081 /** 082 * Returns an LDAP connection for the specified LDAP server. The returned 083 * connection must be closed once the caller has completed its transaction. 084 * Connections are cached between calls using a connection pool. 085 * 086 * @param host The LDAP server host name. 087 * @param port The LDAP server port. 088 * @return An LDAP connection for the specified LDAP server. 089 * @throws ErrorResultException If an error occurred while connecting to the LDAP server. 090 */ 091 public LdapConnection connect(final String host, final int port) throws ErrorResultException { 092 return connect(host, port, new LDAPOptions()); 093 } 094 095 /** 096 * Returns an LDAP connection for the specified LDAP server using the 097 * provided LDAP options. The returned connection must be closed once the 098 * caller has completed its transaction. Connections are cached between 099 * calls using a connection pool. The LDAP options may be used for 100 * configuring SSL parameters and timeouts. 101 * <p> 102 * NOTE: if a connection has already been obtained to the specified LDAP 103 * server then a cached connection will be returned and the LDAP options 104 * will be ignored. 105 * 106 * @param host The LDAP server host name. 107 * @param port The LDAP server port. 108 * @param options The LDAP options. 109 * @return An LDAP connection for the specified LDAP server. 110 * @throws ErrorResultException If an error occurred while connecting to the LDAP server. 111 */ 112 public LdapConnection connect(final String host, final int port, final LDAPOptions options) 113 throws ErrorResultException { 114 final ConnectionFactory factory = getConnectionFactory(host, port, options); 115 return new LdapConnection(factory.getConnection()); 116 } 117 118 /** 119 * Formats an LDAP distinguished name using the provided template and 120 * attribute values. Values will be safely escaped in order to avoid 121 * potential injection attacks. 122 * 123 * @param template The DN template. 124 * @param attributeValues The attribute values to be substituted into the template. 125 * @return The formatted template parsed as a {@code DN}. 126 * @throws org.forgerock.i18n.LocalizedIllegalArgumentException If the formatted template is not a valid LDAP string 127 * representation of a DN. 128 * @see DN#format(String, Object...) 129 */ 130 public String dn(final String template, final Object... attributeValues) { 131 return DN.format(template, attributeValues).toString(); 132 } 133 134 /** 135 * Formats an LDAP filter using the provided template and assertion values. 136 * Values will be safely escaped in order to avoid potential injection 137 * attacks. 138 * 139 * @param template The filter template. 140 * @param assertionValues The assertion values to be substituted into the template. 141 * @return The formatted template parsed as a {@code Filter}. 142 * @throws org.forgerock.i18n.LocalizedIllegalArgumentException If the formatted template is not a valid LDAP string 143 * representation of a filter. 144 * @see Filter#format(String, Object...) 145 */ 146 public String filter(final String template, final Object... assertionValues) { 147 return Filter.format(template, assertionValues).toString(); 148 } 149 150 private ConnectionFactory getConnectionFactory(final String host, final int port, 151 final LDAPOptions options) { 152 final String key = host + ":" + port; 153 ConnectionFactory factory = factories.get(key); 154 if (factory == null) { 155 synchronized (factories) { 156 factory = factories.get(key); 157 if (factory == null) { 158 factory = 159 newCachedConnectionPool(newHeartBeatConnectionFactory(new LDAPConnectionFactory( 160 host, port, options))); 161 factories.put(key, factory); 162 } 163 } 164 } 165 return factory; 166 } 167 168 @Override 169 protected void finalize() throws Throwable { 170 for (ConnectionFactory factory : factories.values()) { 171 factory.close(); 172 } 173 super.finalize(); 174 } 175}