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 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.servlet; 019 020import static java.lang.String.*; 021import static org.forgerock.json.fluent.JsonValue.*; 022import static org.forgerock.openig.audit.AuditSystem.*; 023import static org.forgerock.openig.audit.decoration.AuditDecorator.*; 024import static org.forgerock.openig.config.Environment.*; 025import static org.forgerock.openig.decoration.capture.CaptureDecorator.*; 026import static org.forgerock.openig.decoration.timer.TimerDecorator.*; 027import static org.forgerock.openig.http.HttpClient.*; 028import static org.forgerock.openig.http.SessionFactory.*; 029import static org.forgerock.openig.io.TemporaryStorage.*; 030import static org.forgerock.openig.log.LogSink.*; 031import static org.forgerock.openig.util.Json.*; 032import static org.forgerock.util.Utils.*; 033 034import java.io.File; 035import java.io.FileNotFoundException; 036import java.io.IOException; 037import java.io.InputStream; 038import java.net.URI; 039import java.net.URISyntaxException; 040import java.net.URL; 041import java.util.Arrays; 042import java.util.Collections; 043import java.util.Enumeration; 044 045import javax.servlet.ServletConfig; 046import javax.servlet.ServletException; 047import javax.servlet.http.HttpServlet; 048import javax.servlet.http.HttpServletRequest; 049import javax.servlet.http.HttpServletResponse; 050 051import org.forgerock.json.fluent.JsonValue; 052import org.forgerock.openig.audit.AuditSystem; 053import org.forgerock.openig.audit.decoration.AuditDecorator; 054import org.forgerock.openig.audit.internal.ForwardingAuditSystem; 055import org.forgerock.openig.config.Environment; 056import org.forgerock.openig.decoration.capture.CaptureDecorator; 057import org.forgerock.openig.decoration.timer.TimerDecorator; 058import org.forgerock.openig.handler.Handler; 059import org.forgerock.openig.handler.HandlerException; 060import org.forgerock.openig.heap.HeapImpl; 061import org.forgerock.openig.heap.Name; 062import org.forgerock.openig.http.Exchange; 063import org.forgerock.openig.http.HttpClient; 064import org.forgerock.openig.http.Request; 065import org.forgerock.openig.http.Session; 066import org.forgerock.openig.http.SessionFactory; 067import org.forgerock.openig.io.BranchingStreamWrapper; 068import org.forgerock.openig.io.TemporaryStorage; 069import org.forgerock.openig.log.ConsoleLogSink; 070import org.forgerock.openig.log.LogSink; 071import org.forgerock.openig.log.Logger; 072import org.forgerock.openig.util.CaseInsensitiveSet; 073import org.forgerock.openig.util.URIUtil; 074 075/** 076 * The main OpenIG HTTP Servlet which is responsible for bootstrapping the configuration and delegating all request 077 * processing to the configured handler implementation (for example, a DispatchHandler). 078 * <p> 079 * <pre> 080 * { 081 * "heap": { 082 * ... 083 * }, 084 * "handler": "DispatchHandler", 085 * "baseURI": "http://localhost:8080", 086 * "logSink": "myCustomLogSink", 087 * "temporaryStorage": "myCustomStorage" 088 * } 089 * </pre> 090 * {@literal handler} is the only mandatory configuration attribute. 091 */ 092public class GatewayServlet extends HttpServlet { 093 094 /** Methods that should not include an entity body. */ 095 private static final CaseInsensitiveSet NON_ENTITY_METHODS = new CaseInsensitiveSet(Arrays.asList("GET", "HEAD", 096 "TRACE", "DELETE")); 097 098 private static final long serialVersionUID = 1L; 099 100 /** 101 * Default HttpClient heap object declaration. 102 */ 103 private static final JsonValue DEFAULT_HTTP_CLIENT = json(object(field("name", HTTP_CLIENT_HEAP_KEY), 104 field("type", HttpClient.class.getName()))); 105 106 private static JsonValue readJson(final URL resource) throws ServletException { 107 InputStream in = null; 108 try { 109 in = resource.openStream(); 110 return new JsonValue(readJsonLenient(in)); 111 } catch (final FileNotFoundException e) { 112 throw new ServletException(format("File %s does not exists", resource), e); 113 } catch (final IOException e) { 114 throw new ServletException(format("Cannot read/parse content of %s", resource), e); 115 } finally { 116 closeSilently(in); 117 } 118 } 119 120 /** Overrides request URLs constructed by container; making requests relative to a new base URI. */ 121 private URI baseURI; 122 123 /** 124 * Environment can be provided by the caller or, if null, it will be based on default policy. 125 * <ol> 126 * <li>{@literal openig-base} servlet init-param</li> 127 * <li>{@literal OPENIG_BASE} environment variable</li> 128 * <li>{@literal openig.base} system property</li> 129 * <li>platform specific default directory ({@literal ~/.openig/} or {@literal $AppData$\openig\})</li> 130 * </ol> 131 */ 132 private Environment environment; 133 134 /** The handler to dispatch exchanges to. */ 135 private Handler handler; 136 137 /** 138 * Heap of objects (represents the live configuration). 139 */ 140 private HeapImpl heap; 141 142 /** Provides methods for various logging activities. */ 143 private Logger logger; 144 145 /** Allocates temporary buffers for caching streamed content during request processing. */ 146 private TemporaryStorage storage; 147 148 /** 149 * Factory to create OpenIG sessions. 150 */ 151 private SessionFactory sessionFactory; 152 153 /** 154 * Default constructor invoked from web container. The servlet will be assumed to be running as a web application 155 * and obtain its configuration from the default web {@linkplain Environment environment}. 156 */ 157 public GatewayServlet() { 158 this(null); 159 } 160 161 /** 162 * Creates a new servlet using the provided environment. This constructor should be called when running the servlet 163 * as part of a standalone application. 164 * 165 * @param environment 166 * The application environment. 167 */ 168 public GatewayServlet(final Environment environment) { 169 this.environment = environment; 170 } 171 172 @Override 173 public void destroy() { 174 heap.destroy(); 175 environment = null; 176 } 177 178 @Override 179 public void init(final ServletConfig servletConfig) throws ServletException { 180 super.init(servletConfig); 181 if (environment == null) { 182 environment = new WebEnvironment(servletConfig); 183 } 184 try { 185 // Load the configuration 186 final File configuration = new File(environment.getConfigDirectory(), "config.json"); 187 final URL configurationURL = configuration.canRead() ? configuration.toURI().toURL() : getClass() 188 .getResource("default-config.json"); 189 final JsonValue config = readJson(configurationURL); 190 191 // Create and configure the heap 192 heap = new HeapImpl(Name.of(configurationURL.toString())); 193 // "Live" objects 194 heap.put("ServletContext", servletConfig.getServletContext()); 195 heap.put(ENVIRONMENT_HEAP_KEY, environment); 196 197 AuditSystem auditSystem = new ForwardingAuditSystem(); 198 199 // can be overridden in config 200 heap.put(TEMPORARY_STORAGE_HEAP_KEY, new TemporaryStorage()); 201 heap.put(LOGSINK_HEAP_KEY, new ConsoleLogSink()); 202 heap.put(CAPTURE_HEAP_KEY, new CaptureDecorator(null, false, false)); 203 heap.put(TIMER_HEAP_KEY, new TimerDecorator()); 204 heap.put(AUDIT_HEAP_KEY, new AuditDecorator(auditSystem)); 205 heap.put(AUDIT_SYSTEM_HEAP_KEY, auditSystem); 206 heap.addDefaultDeclaration(DEFAULT_HTTP_CLIENT); 207 heap.init(config, "logSink", "temporaryStorage", "handler", "handlerObject", "baseURI", "globalDecorators"); 208 209 // As all heaplets can specify their own storage and logger, 210 // these two lines provide custom logger or storage available. 211 logger = new Logger(heap.resolve(config.get("logSink").defaultTo(LOGSINK_HEAP_KEY), 212 LogSink.class, true), Name.of("GatewayServlet")); 213 storage = heap.resolve(config.get("temporaryStorage").defaultTo(TEMPORARY_STORAGE_HEAP_KEY), 214 TemporaryStorage.class); 215 // Let the user change the type of session to use 216 sessionFactory = heap.get(SESSION_FACTORY_HEAP_KEY, SessionFactory.class); 217 baseURI = config.get("baseURI").asURI(); 218 handler = heap.getHandler(); 219 } catch (final ServletException e) { 220 throw e; 221 } catch (final Exception e) { 222 throw new ServletException(e); 223 } 224 } 225 226 /** 227 * Handles a servlet request by dispatching it to a handler. It receives a servlet request, translates it into an 228 * exchange object, dispatches the exchange to a handler, then translates the exchange response into the servlet 229 * response. 230 * 231 * @param request 232 * the {@link HttpServletRequest} object that will be used to populate the initial OpenIG's 233 * {@link Request} encapsulated in the {@link Exchange}. 234 * @param response 235 * the {@link HttpServletResponse} object that contains the response the servlet returns to the client. 236 * @exception IOException 237 * if an input or output error occurs while the servlet is handling the HTTP request. 238 * @exception ServletException 239 * if the HTTP request cannot be handled. 240 */ 241 @SuppressWarnings("unchecked") 242 @Override 243 public void service(final HttpServletRequest request, final HttpServletResponse response) throws IOException, 244 ServletException { 245 246 // Build Exchange 247 URI uri = createRequestUri(request); 248 final Exchange exchange = new Exchange(uri); 249 250 // populate request 251 exchange.request = new Request(); 252 exchange.request.setMethod(request.getMethod()); 253 exchange.request.setUri(uri); 254 if (baseURI != null) { 255 exchange.request.getUri().rebase(baseURI); 256 } 257 258 // request headers 259 for (final Enumeration<String> e = request.getHeaderNames(); e.hasMoreElements(); ) { 260 final String name = e.nextElement(); 261 exchange.request.getHeaders().addAll(name, Collections.list(request.getHeaders(name))); 262 } 263 264 // include request entity if appears to be provided with request 265 if ((request.getContentLength() > 0 || request.getHeader("Transfer-Encoding") != null) 266 && !NON_ENTITY_METHODS.contains(exchange.request.getMethod())) { 267 exchange.request.setEntity(new BranchingStreamWrapper(request.getInputStream(), storage)); 268 } 269 exchange.clientInfo = new ServletClientInfo(request); 270 // TODO consider moving this below (when the exchange will be fully configured) 271 exchange.session = newSession(request, exchange); 272 exchange.principal = request.getUserPrincipal(); 273 // handy servlet-specific attributes, sure to be abused by downstream filters 274 exchange.put(HttpServletRequest.class.getName(), request); 275 exchange.put(HttpServletResponse.class.getName(), response); 276 try { 277 // handle request 278 try { 279 handler.handle(exchange); 280 } catch (final HandlerException he) { 281 throw new ServletException(he); 282 } finally { 283 // Close the session before writing back the actual response message to the User-Agent 284 closeSilently(exchange.session); 285 } 286 /* 287 * Support for OPENIG-94/95 - The wrapped servlet may have already committed its response w/o creating a new 288 * OpenIG Response instance in the exchange. 289 */ 290 if (exchange.response != null) { 291 // response status-code (reason-phrase deprecated in Servlet API) 292 response.setStatus(exchange.response.getStatus()); 293 294 // response headers 295 for (final String name : exchange.response.getHeaders().keySet()) { 296 for (final String value : exchange.response.getHeaders().get(name)) { 297 if (value != null && value.length() > 0) { 298 response.addHeader(name, value); 299 } 300 } 301 } 302 // response entity (if applicable) 303 exchange.response.getEntity().copyRawContentTo(response.getOutputStream()); 304 } 305 } finally { 306 // final cleanup 307 closeSilently(exchange.request, exchange.response); 308 } 309 } 310 311 private static URI createRequestUri(final HttpServletRequest request) throws ServletException { 312 try { 313 return URIUtil.create(request.getScheme(), 314 null, 315 request.getServerName(), 316 request.getServerPort(), 317 request.getRequestURI(), 318 request.getQueryString(), 319 null); 320 } catch (final URISyntaxException use) { 321 throw new ServletException(use); 322 } 323 } 324 325 private Session newSession(final HttpServletRequest request, final Exchange exchange) { 326 if (sessionFactory != null) { 327 return sessionFactory.build(exchange); 328 } 329 return new ServletSession(request); 330 } 331}