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}