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}