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 2013 ForgeRock AS.
015 */
016package org.forgerock.opendj.rest2ldap.servlet;
017
018import static org.forgerock.json.resource.Resources.*;
019import static org.forgerock.opendj.rest2ldap.Rest2LDAP.*;
020
021import java.io.InputStream;
022import java.util.Map;
023
024import javax.servlet.ServletConfig;
025import javax.servlet.ServletException;
026
027import org.codehaus.jackson.JsonParser;
028import org.codehaus.jackson.map.ObjectMapper;
029import org.forgerock.json.fluent.JsonValue;
030import org.forgerock.json.resource.CollectionResourceProvider;
031import org.forgerock.json.resource.Connection;
032import org.forgerock.json.resource.ConnectionFactory;
033import org.forgerock.json.resource.FutureResult;
034import org.forgerock.json.resource.ResourceException;
035import org.forgerock.json.resource.ResultHandler;
036import org.forgerock.json.resource.Router;
037import org.forgerock.opendj.rest2ldap.AuthorizationPolicy;
038import org.forgerock.opendj.rest2ldap.Rest2LDAP;
039import org.forgerock.opendj.rest2ldap.Rest2LDAP.Builder;
040import org.forgerock.util.Utils;
041
042/**
043 * The connection factory provider which is used by the OpenDJ Commons REST LDAP
044 * Gateway.
045 */
046public final class Rest2LDAPConnectionFactoryProvider {
047    private static final String INIT_PARAM_CONFIG_FILE = "config-file";
048    private static final ObjectMapper JSON_MAPPER = new ObjectMapper().configure(
049            JsonParser.Feature.ALLOW_COMMENTS, true);
050
051    /**
052     * Returns a JSON resource connection factory configured using the
053     * configuration file named in the {@code config-file} Servlet
054     * initialization parameter. See the sample configuration file for a
055     * detailed description of its content.
056     *
057     * @param config
058     *            The Servlet configuration.
059     * @return The configured JSON resource connection factory.
060     * @throws ServletException
061     *             If the connection factory could not be initialized.
062     * @see Rest2LDAP#configureConnectionFactory(JsonValue, String)
063     * @see Builder#configureMapping(JsonValue)
064     */
065    public static ConnectionFactory getConnectionFactory(final ServletConfig config)
066            throws ServletException {
067        final String configFileName = config.getInitParameter(INIT_PARAM_CONFIG_FILE);
068        if (configFileName == null) {
069            throw new ServletException("Servlet initialization parameter '"
070                    + INIT_PARAM_CONFIG_FILE + "' not specified");
071        }
072        final InputStream configFile =
073                config.getServletContext().getResourceAsStream(configFileName);
074        if (configFile == null) {
075            throw new ServletException("Servlet configuration file '" + configFileName
076                    + "' not found");
077        }
078        try {
079            // Parse the config file.
080            final Object content = JSON_MAPPER.readValue(configFile, Object.class);
081            if (!(content instanceof Map)) {
082                throw new ServletException("Servlet configuration file '" + configFileName
083                        + "' does not contain a valid JSON configuration");
084            }
085            final JsonValue configuration = new JsonValue(content);
086
087            // Parse the authorization configuration.
088            final AuthorizationPolicy authzPolicy =
089                    configuration.get("servlet").get("authorizationPolicy").required().asEnum(
090                            AuthorizationPolicy.class);
091            final String proxyAuthzTemplate =
092                    configuration.get("servlet").get("proxyAuthzIdTemplate").asString();
093
094            // Parse the connection factory if present.
095            final String ldapFactoryName =
096                    configuration.get("servlet").get("ldapConnectionFactory").asString();
097            final org.forgerock.opendj.ldap.ConnectionFactory ldapFactory;
098            if (ldapFactoryName != null) {
099                ldapFactory =
100                        configureConnectionFactory(configuration.get("ldapConnectionFactories")
101                                .required(), ldapFactoryName);
102            } else {
103                ldapFactory = null;
104            }
105
106            // Create the router.
107            final Router router = new Router();
108            final JsonValue mappings = configuration.get("servlet").get("mappings").required();
109            for (final String mappingUrl : mappings.keys()) {
110                final JsonValue mapping = mappings.get(mappingUrl);
111                final CollectionResourceProvider provider =
112                        Rest2LDAP.builder().ldapConnectionFactory(ldapFactory).authorizationPolicy(
113                                authzPolicy).proxyAuthzIdTemplate(proxyAuthzTemplate)
114                                .configureMapping(mapping).build();
115                router.addRoute(mappingUrl, provider);
116            }
117            final ConnectionFactory factory = newInternalConnectionFactory(router);
118            if (ldapFactory != null) {
119                /*
120                 * Return a wrapper which will release resources associated with
121                 * the LDAP connection factory (pooled connections, transport,
122                 * etc).
123                 */
124                return new ConnectionFactory() {
125                    @Override
126                    public FutureResult<Connection> getConnectionAsync(
127                            ResultHandler<? super Connection> handler) {
128                        return factory.getConnectionAsync(handler);
129                    }
130
131                    @Override
132                    public Connection getConnection() throws ResourceException {
133                        return factory.getConnection();
134                    }
135
136                    @Override
137                    public void close() {
138                        ldapFactory.close();
139                    }
140                };
141            } else {
142                return factory;
143            }
144        } catch (final ServletException e) {
145            // Rethrow.
146            throw e;
147        } catch (final Exception e) {
148            throw new ServletException("Servlet configuration file '" + configFileName
149                    + "' could not be read: " + e.getMessage());
150        } finally {
151            Utils.closeSilently(configFile);
152        }
153    }
154
155    /** Prevent instantiation. */
156    private Rest2LDAPConnectionFactoryProvider() {
157        // Nothing to do.
158    }
159
160}