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 2012-2015 ForgeRock AS.
015 */
016
017package org.forgerock.json.resource.http;
018
019import java.util.Collections;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.TreeMap;
025
026import org.forgerock.services.context.AbstractContext;
027import org.forgerock.services.context.Context;
028import org.forgerock.http.protocol.Header;
029import org.forgerock.json.JsonValue;
030import org.forgerock.util.Factory;
031import org.forgerock.util.LazyMap;
032
033/** A {@link Context} containing information relating to the originating HTTP request. */
034public final class HttpContext extends AbstractContext {
035
036    // TODO: security parameters such as user name, etc?
037    /**
038     * Attribute in the serialized JSON form that holds the request headers.
039     * @see #HttpContext(JsonValue, ClassLoader)
040     */
041    public static final String ATTR_HEADERS = "headers";
042
043    /**
044     * Attribute in the serialized JSON form that holds the query and/or form parameters.
045     * @see #HttpContext(JsonValue, ClassLoader)
046     */
047    public static final String ATTR_PARAMETERS = "parameters";
048
049    /**
050     * Attribute in the serialised JSON form that holds the HTTP method of the request.
051     * @see #HttpContext(JsonValue, ClassLoader)
052     */
053    public static final String ATTR_METHOD = "method";
054
055    /**
056     * Attribute in the serialised JSON form that holds the full URI of the request, excluding anything beyond the
057     * path component (i.e., no query parameters).
058     * @see #HttpContext(JsonValue, ClassLoader)
059     */
060    public static final String ATTR_PATH = "path";
061
062    private final Map<String, List<String>> headers;
063    private final Map<String, List<String>> parameters;
064
065    HttpContext(Context parent, final org.forgerock.http.protocol.Request req) {
066        super(parent, "http");
067        data.put(ATTR_METHOD, HttpUtils.getMethod(req));
068        data.put(ATTR_PATH, getRequestPath(req));
069        this.headers = Collections.unmodifiableMap(new LazyMap<>(
070            new Factory<Map<String, List<String>>>() {
071                @Override
072                public Map<String, List<String>> newInstance() {
073                    Map<String, List<String>> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
074                    for (Map.Entry<String, Header> header : req.getHeaders().asMapOfHeaders().entrySet()) {
075                        String name = header.getKey();
076                        List<String> values = header.getValue().getValues();
077                        result.put(name, values);
078                    }
079                    return result;
080                }
081            }));
082        data.put(ATTR_HEADERS, headers);
083        this.parameters = Collections.unmodifiableMap(new LazyMap<>(
084            new Factory<Map<String, List<String>>>() {
085                @Override
086                public Map<String, List<String>> newInstance() {
087                    Map<String, List<String>> result = new LinkedHashMap<>();
088                    Set<Map.Entry<String, List<String>>> parameters = req.getForm().entrySet();
089                    for (Map.Entry<String, List<String>> parameter : parameters) {
090                        String name = parameter.getKey();
091                        List<String> values = parameter.getValue();
092                        result.put(name, values);
093                    }
094                    return result;
095                }
096            }));
097        data.put(ATTR_PARAMETERS, parameters);
098    }
099
100    /**
101     * Restore from JSON representation.
102     *
103     * @param savedContext
104     *            The JSON representation from which this context's attributes
105     *            should be parsed. Must be a JSON Object that contains {@link #ATTR_HEADERS} and
106     *            {@link #ATTR_PARAMETERS} attributes.
107     * @param classLoader
108     *            The ClassLoader which can properly resolve the persisted class-name.
109     */
110    public HttpContext(final JsonValue savedContext, final ClassLoader classLoader) {
111        super(savedContext, classLoader);
112        this.headers = data.get(ATTR_HEADERS).required().asMapOfList(String.class);
113        this.parameters = data.get(ATTR_PARAMETERS).required().asMapOfList(String.class);
114    }
115
116    private String getRequestPath(org.forgerock.http.protocol.Request req) {
117        return new StringBuilder()
118            .append(req.getUri().getScheme())
119            .append("://")
120            .append(req.getUri().getRawAuthority())
121            .append(req.getUri().getRawPath()).toString();
122    }
123
124    /**
125     * Returns an unmodifiable list containing the values of the named HTTP
126     * request header.
127     *
128     * @param name
129     *            The name of the HTTP request header.
130     * @return An unmodifiable list containing the values of the named HTTP
131     *         request header, which may be empty if the header is not present
132     *         in the request.
133     */
134    public List<String> getHeader(String name) {
135        List<String> header = headers.get(name);
136        return Collections.unmodifiableList(header != null ? header : Collections.<String> emptyList());
137    }
138
139    /**
140     * Returns the first value of the named HTTP request header.
141     *
142     * @param name
143     *            The name of the HTTP request header.
144     * @return The first value of the named HTTP request header, or {@code null}
145     *         if the header is not present in the request.
146     */
147    public String getHeaderAsString(String name) {
148        List<String> header = getHeader(name);
149        return header.isEmpty() ? null : header.get(0);
150    }
151
152    /**
153     * Returns an unmodifiable map of the HTTP request headers.
154     *
155     * @return An unmodifiable map of the HTTP request headers.
156     */
157    public Map<String, List<String>> getHeaders() {
158        return headers;
159    }
160
161    /**
162     * Returns the effective HTTP method, taking into account presence of the
163     * {@code X-HTTP-Method-Override} header.
164     *
165     * @return The effective HTTP method, taking into account presence of the
166     *         {@code X-HTTP-Method-Override} header.
167     */
168    public String getMethod() {
169        return data.get(ATTR_METHOD).asString();
170    }
171
172    /**
173     * Returns an unmodifiable list containing the values of the named HTTP
174     * request parameter.
175     *
176     * @param name
177     *            The name of the HTTP request parameter.
178     * @return An unmodifiable list containing the values of the named HTTP
179     *         request parameter, which may be empty if the parameter is not
180     *         present in the request.
181     */
182    public List<String> getParameter(String name) {
183        final List<String> parameter = parameters.get(name);
184        return Collections.unmodifiableList(parameter != null ? parameter : Collections.<String> emptyList());
185    }
186
187    /**
188     * Returns the first value of the named HTTP request parameter.
189     *
190     * @param name
191     *            The name of the HTTP request parameter.
192     * @return The first value of the named HTTP request parameter, or
193     *         {@code null} if the parameter is not present in the request.
194     */
195    public String getParameterAsString(String name) {
196        final List<String> parameter = getParameter(name);
197        return parameter.isEmpty() ? null : parameter.get(0);
198    }
199
200    /**
201     * Returns an unmodifiable map of the HTTP request parameters.
202     *
203     * @return An unmodifiable map of the HTTP request parameters.
204     */
205    public Map<String, List<String>> getParameters() {
206        return parameters;
207    }
208
209    /**
210     * Returns the HTTP request path.
211     *
212     * @return The HTTP request path.
213     */
214    public String getPath() {
215        return data.get(ATTR_PATH).asString();
216    }
217}