All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.rest.RestSession Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.rest;

import static org.apache.juneau.internal.CollectionUtils.*;

import java.io.*;
import java.util.*;

import jakarta.servlet.http.*;

import org.apache.http.*;
import org.apache.juneau.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.http.response.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.logger.*;
import org.apache.juneau.rest.util.*;

/**
 * Represents a single HTTP request.
 *
 * 
Notes:
    *
  • This class is not thread safe. *
* *
See Also:
    *
*/ public class RestSession extends ContextSession { //----------------------------------------------------------------------------------------------------------------- // Static //----------------------------------------------------------------------------------------------------------------- /** * Request attribute name for passing path variables from parent to child. */ private static final String REST_PATHVARS_ATTR = "juneau.pathVars"; /** * Creates a builder of this object. * * @param ctx The context creating this builder. * @return A new builder. */ public static Builder create(RestContext ctx) { return new Builder(ctx); } //----------------------------------------------------------------------------------------------------------------- // Builder //----------------------------------------------------------------------------------------------------------------- /** * Builder class. */ public static class Builder extends ContextSession.Builder { RestContext ctx; Object resource; HttpServletRequest req; HttpServletResponse res; CallLogger logger; String pathInfoUndecoded; UrlPath urlPath; /** * Constructor. * * @param ctx The context creating this session. */ protected Builder(RestContext ctx) { super(ctx); this.ctx = ctx; } /** * Specifies the servlet implementation bean. * * @param value The value for this setting. * @return This object. */ public Builder resource(Object value) { resource = value; return this; } /** * Specifies the HTTP servlet request object on this call. * * @param value The value for this setting. * @return This object. */ public Builder req(HttpServletRequest value) { req = value; return this; } /** * Returns the HTTP servlet request object on this call. * * @return The HTTP servlet request object on this call. */ public HttpServletRequest req() { urlPath = null; pathInfoUndecoded = null; return req; } /** * Specifies the HTTP servlet response object on this call. * * @param value The value for this setting. * @return This object. */ public Builder res(HttpServletResponse value) { res = value; return this; } /** * Returns the HTTP servlet response object on this call. * * @return The HTTP servlet response object on this call. */ public HttpServletResponse res() { return res; } /** * Specifies the logger to use for this session. * * @param value The value for this setting. * @return This object. */ public Builder logger(CallLogger value) { logger = value; return this; } @Override /* Session.Builder */ public RestSession build() { return new RestSession(this); } /** * Returns the request path info as a {@link UrlPath} bean. * * @return The request path info as a {@link UrlPath} bean. */ public UrlPath getUrlPath() { if (urlPath == null) urlPath = UrlPath.of(getPathInfoUndecoded()); return urlPath; } /** * Returns the request path info as a {@link UrlPath} bean. * * @return The request path info as a {@link UrlPath} bean. */ public String getPathInfoUndecoded() { if (pathInfoUndecoded == null) pathInfoUndecoded = RestUtils.getPathInfoUndecoded(req); return pathInfoUndecoded; } /** * Adds resolved @Resource(path) variable values to this call. * * @param value The variables to add to this call. * @return This object. */ @SuppressWarnings("unchecked") public Builder pathVars(Map value) { if (value != null && ! value.isEmpty()) { Map m = (Map)req.getAttribute(REST_PATHVARS_ATTR); if (m == null) { m = new TreeMap<>(); req.setAttribute(REST_PATHVARS_ATTR, m); } m.putAll(value); } return this; } } //----------------------------------------------------------------------------------------------------------------- // Static //----------------------------------------------------------------------------------------------------------------- private final Object resource; private final RestContext context; private HttpServletRequest req; private HttpServletResponse res; private CallLogger logger; private UrlPath urlPath; private String pathInfoUndecoded; private long startTime = System.currentTimeMillis(); private BeanStore beanStore; private Map queryParams; private String method; private RestOpSession opSession; private UrlPathMatch urlPathMatch; /** * Constructor. * * @param builder The builder for this object. */ public RestSession(Builder builder) { super(builder); context = builder.ctx; resource = builder.resource; beanStore = BeanStore.of(context.getBeanStore(), resource).addBean(RestContext.class, context); req = beanStore.add(HttpServletRequest.class, builder.req); res = beanStore.add(HttpServletResponse.class, builder.res); logger = beanStore.add(CallLogger.class, builder.logger); urlPath = beanStore.add(UrlPath.class, builder.urlPath); pathInfoUndecoded = builder.pathInfoUndecoded; } //------------------------------------------------------------------------------------------------------------------ // Fluent setters. //------------------------------------------------------------------------------------------------------------------ /** * Sets the logger to use when logging this call. * * @param value The new value for this setting. Can be null. * @return This object. */ public RestSession logger(CallLogger value) { logger = beanStore.add(CallLogger.class, value); return this; } /** * Enables or disabled debug mode on this call. * * @param value The new value for this setting. * @return This object. * @throws IOException Occurs if request content could not be cached into memory. */ public RestSession debug(boolean value) throws IOException { if (value) { req = CachingHttpServletRequest.wrap(req); res = CachingHttpServletResponse.wrap(res); req.setAttribute("Debug", true); } else { req.removeAttribute("Debug"); } return this; } /** * Sets the HTTP status on this call. * * @param value The status code. * @return This object. */ public RestSession status(int value) { res.setStatus(value); return this; } /** * Sets the HTTP status on this call. * * @param value The status code. * @return This object. */ public RestSession status(StatusLine value) { if (value != null) res.setStatus(value.getStatusCode()); return this; } /** * Identifies that an exception occurred during this call. * * @param value The thrown exception. * @return This object. */ public RestSession exception(Throwable value) { req.setAttribute("Exception", value); beanStore.addBean(Throwable.class, value); return this; } /** * Sets the URL path pattern match on this call. * * @param value The match pattern. * @return This object. */ public RestSession urlPathMatch(UrlPathMatch value) { urlPathMatch = beanStore.add(UrlPathMatch.class, value); return this; } //------------------------------------------------------------------------------------------------------------------ // Getters //------------------------------------------------------------------------------------------------------------------ /** * Returns the HTTP servlet request of this REST call. * * @return the HTTP servlet request of this REST call. */ public HttpServletRequest getRequest() { return req; } /** * Returns the HTTP servlet response of this REST call. * * @return the HTTP servlet response of this REST call. */ public HttpServletResponse getResponse() { return res; } /** * Returns the bean store of this call. * * @return The bean store of this call. */ public BeanStore getBeanStore() { return beanStore; } /** * Returns resolved @Resource(path) variable values on this call. * * @return Resolved @Resource(path) variable values on this call. */ @SuppressWarnings("unchecked") public Map getPathVars() { Map m = (Map)req.getAttribute(REST_PATHVARS_ATTR); return m == null ? Collections.emptyMap() : m; } /** * Returns the URL path pattern match on this call. * * @return The URL path pattern match on this call. */ public UrlPathMatch getUrlPathMatch() { return urlPathMatch; } /** * Returns the exception that occurred during this call. * * @return The exception that occurred during this call. */ public Throwable getException() { return (Throwable)req.getAttribute("Exception"); } //------------------------------------------------------------------------------------------------------------------ // Lifecycle methods. //------------------------------------------------------------------------------------------------------------------ /** * Called at the end of a call to finish any remaining tasks such as flushing buffers and logging the response. * * @return This object. */ public RestSession finish() { try { req.setAttribute("ExecTime", System.currentTimeMillis() - startTime); if (opSession != null) opSession.finish(); else { res.flushBuffer(); } } catch (Exception e) { exception(e); } if (logger != null) logger.log(req, res); return this; } //------------------------------------------------------------------------------------------------------------------ // Pass-through convenience methods. //------------------------------------------------------------------------------------------------------------------ /** * Shortcut for calling getRequest().getServletPath(). * * @return The request servlet path. */ public String getServletPath() { return req.getServletPath(); } /** * Returns the request path info as a {@link UrlPath} bean. * * @return The request path info as a {@link UrlPath} bean. */ public UrlPath getUrlPath() { if (urlPath == null) urlPath = UrlPath.of(getPathInfoUndecoded()); return urlPath; } /** * Shortcut for calling getRequest().getPathInfo(). * * @return The request servlet path info. */ public String getPathInfo() { return req.getPathInfo(); } /** * Same as {@link #getPathInfo()} but doesn't decode encoded characters. * * @return The undecoded request servlet path info. */ public String getPathInfoUndecoded() { if (pathInfoUndecoded == null) pathInfoUndecoded = RestUtils.getPathInfoUndecoded(req); return pathInfoUndecoded; } /** * Returns the query parameters on the request. * *

* Unlike {@link HttpServletRequest#getParameterMap()}, this doesn't parse the content if it's a POST. * * @return The query parameters on the request. */ public Map getQueryParams() { if (queryParams == null) { if (req.getMethod().equalsIgnoreCase("POST")) queryParams = RestUtils.parseQuery(req.getQueryString(), map()); else queryParams = req.getParameterMap(); } return queryParams; } /** * Returns the HTTP method name. * * @return The HTTP method name, always uppercased. */ public String getMethod() { if (method == null) { Set s1 = context.getAllowedMethodParams(); Set s2 = context.getAllowedMethodHeaders(); if (! s1.isEmpty()) { String[] x = getQueryParams().get("method"); if (x != null && (s1.contains("*") || s1.contains(x[0]))) method = x[0]; } if (method == null && ! s2.isEmpty()) { String x = req.getHeader("X-Method"); if (x != null && (s2.contains("*") || s2.contains(x))) method = x; } if (method == null) method = req.getMethod(); method = method.toUpperCase(Locale.ENGLISH); } return method; } /** * Shortcut for calling getRequest().getStatus(). * * @return The response status code. */ public int getStatus() { return res.getStatus(); } /** * Returns the context that created this call. * * @return The context that created this call. */ @Override public RestContext getContext() { return context; } /** * Returns the REST object. * * @return The rest object. */ public Object getResource() { return resource; } /** * Returns the operation session of this REST session. * *

* The operation session is created once the Java method to be invoked has been determined. * * @return The operation session of this REST session. * @throws InternalServerError If operation session has not been created yet. */ public RestOpSession getOpSession() throws InternalServerError { if (opSession == null) throw new InternalServerError("Op Session not created."); return opSession; } /** * Runs this session. * *

* Does the following: *

    *
  1. Finds the Java method to invoke and creates a {@link RestOpSession} for it. *
  2. Invokes {@link RestPreCall} methods by calling {@link RestContext#preCall(RestOpSession)}. *
  3. Invokes Java method by calling {@link RestOpSession#run()}. *
  4. Invokes {@link RestPostCall} methods by calling {@link RestContext#postCall(RestOpSession)}. *
  5. If the Java method produced output, finds the response processor for it and runs it by calling {@link RestContext#processResponse(RestOpSession)}. *
  6. If no Java method matched, generates a 404/405/412 by calling {@link RestContext#handleNotFound(RestSession)}. *
* * @throws Throwable Any throwable can be thrown. */ public void run() throws Throwable { try { opSession = context.getRestOperations().findOperation(this).createSession(this).build(); context.preCall(opSession); opSession.run(); context.postCall(opSession); if (res.getStatus() == 0) res.setStatus(200); if (opSession.getResponse().hasContent()) { // Now serialize the output if there was any. // Some subclasses may write to the OutputStream or Writer directly. context.processResponse(opSession); } } catch (NotFound e) { if (getStatus() == 0) status(404); exception(e); context.handleNotFound(this); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy