blob: 263b5309b09e8836d8c03009782a89232842e886 [file] [log] [blame]
// ***************************************************************************************************************************
// * 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 java.util.Collections.*;
import static java.util.logging.Level.*;
import static org.apache.juneau.html.HtmlDocSerializer.*;
import static org.apache.juneau.httppart.HttpPartType.*;
import static org.apache.juneau.internal.IOUtils.*;
import static org.apache.juneau.serializer.Serializer.*;
import java.io.*;
import java.lang.reflect.*;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.*;
import java.nio.charset.*;
import java.text.*;
import java.util.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.juneau.*;
import org.apache.juneau.config.*;
import org.apache.juneau.dto.swagger.*;
import org.apache.juneau.http.*;
import org.apache.juneau.http.ReaderResource;
import org.apache.juneau.http.StreamResource;
import org.apache.juneau.http.annotation.*;
import org.apache.juneau.http.annotation.Body;
import org.apache.juneau.http.annotation.FormData;
import org.apache.juneau.http.annotation.Header;
import org.apache.juneau.http.annotation.Path;
import org.apache.juneau.http.annotation.Query;
import org.apache.juneau.http.annotation.Response;
import org.apache.juneau.httppart.*;
import org.apache.juneau.httppart.bean.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.jsonschema.*;
import org.apache.juneau.oapi.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.remote.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.exception.*;
import org.apache.juneau.rest.helper.*;
import org.apache.juneau.rest.util.*;
import org.apache.juneau.rest.widget.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;
import org.apache.juneau.uon.*;
import org.apache.juneau.utils.*;
/**
* Represents an HTTP request for a REST resource.
*
* <p>
* Equivalent to {@link HttpServletRequest} except with some additional convenience methods.
*
* <p>
* For reference, given the URL <js>"http://localhost:9080/contextRoot/servletPath/foo?bar=baz#qux"</js>, the
* following methods return the following values....
* <table class='styled'>
* <tr><th>Method</th><th>Value</th></tr>
* <tr><td>{@code getContextPath()}</td><td>{@code /contextRoot}</td></tr>
* <tr><td>{@code getPathInfo()}</td><td>{@code /foo}</td></tr>
* <tr><td>{@code getPathTranslated()}</td><td>{@code path-to-deployed-war-on-filesystem/foo}</td></tr>
* <tr><td>{@code getQueryString()}</td><td>{@code bar=baz}</td></tr>
* <tr><td>{@code getRequestURI()}</td><td>{@code /contextRoot/servletPath/foo}</td></tr>
* <tr><td>{@code getRequestURL()}</td><td>{@code http://localhost:9080/contextRoot/servletPath/foo}</td></tr>
* <tr><td>{@code getServletPath()}</td><td>{@code /servletPath}</td></tr>
* </table>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.RestMethod.RestRequest}
* </ul>
*/
@SuppressWarnings({ "unchecked", "unused" })
public final class RestRequest extends HttpServletRequestWrapper {
private HttpServletRequest inner;
private final RestContext context;
private RestMethodContext restJavaMethod;
private final String method;
private RequestBody body;
private Method javaMethod;
@SuppressWarnings("deprecation")
private RequestProperties properties;
private BeanSession beanSession;
private VarResolverSession varSession;
private final RequestQuery queryParams;
private RequestFormData formData;
private RequestPath pathParams;
private boolean isPost;
private UriContext uriContext;
private String charset, authorityPath;
private RequestHeaders headers;
private RequestAttributes attributes;
private Config cf;
private Swagger swagger;
private SerializerSessionArgs serializerSessionArgs;
private ParserSessionArgs parserSessionArgs;
private RestResponse res;
private boolean debug;
/**
* Constructor.
*/
RestRequest(RestContext context, HttpServletRequest req) throws ServletException {
super(req);
this.inner = req;
this.context = context;
try {
isPost = req.getMethod().equalsIgnoreCase("POST");
// If this is a POST, we want to parse the query parameters ourselves to prevent
// the servlet code from processing the HTTP body as URL-Encoded parameters.
queryParams = new RequestQuery(this);
if (isPost)
RestUtils.parseQuery(getQueryString(), queryParams);
else
queryParams.putAll(req.getParameterMap());
// Get the HTTP method.
// Can be overridden through a "method" GET attribute.
String _method = super.getMethod();
String m = getQuery().getString("method");
if (m != null) {
Set<String> s = context.getAllowedMethodParams();
if (! s.isEmpty() && (s.contains("*") || s.contains(m)))
_method = m;
}
m = req.getHeader("X-Method");
if (m != null) {
Set<String> s = context.getAllowedMethodHeaders();
if (! s.isEmpty() && (s.contains("*") || s.contains(m)))
_method = m;
}
method = _method;
headers = new RequestHeaders(this);
for (Enumeration<String> e = getHeaderNames(); e.hasMoreElements();) {
String name = e.nextElement();
headers.put(name, super.getHeaders(name));
}
body = new RequestBody(this);
if (context.isAllowBodyParam()) {
String b = getQuery().getString("body");
if (b != null) {
headers.put("Content-Type", UonSerializer.DEFAULT.getResponseContentType());
body.load(MediaType.UON, UonParser.DEFAULT, b.getBytes(UTF8));
}
}
Set<String> s = context.getAllowedHeaderParams();
if (! s.isEmpty())
headers.queryParams(queryParams, s);
this.pathParams = new RequestPath(this);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
/*
* Called from RestServlet after a match has been made but before the guard or method invocation.
*/
final void init(RestMethodContext rjm, @SuppressWarnings("deprecation") RequestProperties properties) throws IOException {
this.restJavaMethod = rjm;
this.javaMethod = rjm.method;
this.properties = properties;
this.beanSession = rjm.createSession();
this.pathParams
.parser(rjm.partParser);
this.queryParams
.addDefault(rjm.defaultQuery)
.parser(rjm.partParser);
this.headers
.addDefault(rjm.defaultRequestHeaders)
.addDefault(context.getDefaultRequestHeaders())
.parser(rjm.partParser);
this.attributes = new RequestAttributes(this, rjm.defaultRequestAttributes);
this.body
.encoders(rjm.encoders)
.parsers(rjm.parsers)
.headers(headers)
.maxInput(rjm.maxInput);
if (isDebug(rjm)) {
inner = CachingHttpServletRequest.wrap(inner);
setAttribute("Debug", true);
}
debug = "true".equalsIgnoreCase((String)getAttribute("Debug"));
String stylesheet = getQuery().getString("stylesheet");
if (stylesheet != null)
getSession().setAttribute("stylesheet", stylesheet.replace(' ', '$')); // Prevent SVL insertion.
stylesheet = (String)getSession().getAttribute("stylesheet");
if (stylesheet != null)
properties.put(HTMLDOC_stylesheet, new String[]{stylesheet});
}
private boolean isDebug(RestMethodContext context) {
if (! context.isDebug())
return false;
String debugHeader = context.getDebugHeader(), debugParam = context.getDebugParam();
if (debugHeader == null && debugParam == null)
return true;
if (debugHeader != null && "true".equalsIgnoreCase(getHeader(debugHeader)))
return true;
if (debugParam != null && "true".equalsIgnoreCase(getParameter(debugParam)))
return true;
return false;
}
RestRequest setResponse(RestResponse res) {
this.res = res;
return this;
}
/**
* Returns a string of the form <js>"HTTP method-name full-url"</js>
*
* @return A description string of the request.
*/
public String getDescription() {
String qs = getQueryString();
return "HTTP " + getMethod() + " " + getRequestURI() + (qs == null ? "" : "?" + qs);
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Retrieve the properties active for this request.
*
* <p>
* This contains all resource and method level properties from the following:
* <ul>
* <li class='ja'>{@link RestResource#properties()}
* <li class='ja'>{@link RestMethod#properties()}
* <li class='jm'>{@link RestContextBuilder#set(String, Object)}
* </ul>
*
* <p>
* The returned object is modifiable and allows you to override session-level properties before
* they get passed to the serializers.
* <br>However, properties are open-ended, and can be used for any purpose.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(
* properties={
* <ja>@Property</ja>(name=<jsf>SERIALIZER_sortMaps</jsf>, value=<js>"false"</js>)
* }
* )
* <jk>public</jk> Map doGet(RestRequest req, <ja>@Query</ja>(<js>"sortMaps"</js>) Boolean sortMaps) {
*
* <jc>// Override value if specified through query parameter.</jc>
* <jk>if</jk> (sortMaps != <jk>null</jk>)
* req.getProperties().put(<jsf>SERIALIZER_sortMaps</jsf>, sortMaps);
*
* <jk>return</jk> <jsm>getMyMap</jsm>();
* }
* </p>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='jm'>{@link #prop(String, Object)}
* <li class='link'>{@doc juneau-rest-server.Properties}
* </ul>
*
* @return The properties active for this request.
* @deprecated Use {@link #getAttributes()}
*/
@Deprecated
public RequestProperties getProperties() {
return this.properties;
}
/**
* Shortcut for calling <c>getProperties().append(name, value);</c> fluently.
*
* @param name The property name.
* @param value The property value.
* @return This object (for method chaining).
* @deprecated Use {@link RequestAttributes#put(String, Object)} or {@link #setAttribute(String, Object)}.
*/
@Deprecated
public RestRequest prop(String name, Object value) {
this.properties.append(name, value);
return this;
}
//-----------------------------------------------------------------------------------------------------------------
// Headers
//-----------------------------------------------------------------------------------------------------------------
/**
* Request headers.
*
* <p>
* Returns a {@link RequestHeaders} object that encapsulates access to HTTP headers on the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public</jk> Object myMethod(RestRequest req) {
*
* <jc>// Get access to headers.</jc>
* RequestHeaders h = req.getHeaders();
*
* <jc>// Add a default value.</jc>
* h.addDefault(<js>"ETag"</js>, <jsf>DEFAULT_UUID</jsf>);
*
* <jc>// Get a header value as a POJO.</jc>
* UUID etag = h.get(<js>"ETag"</js>, UUID.<jk>class</jk>);
*
* <jc>// Get a standard header.</jc>
* CacheControl = h.getCacheControl();
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* This object is modifiable.
* <li>
* Values are converted from strings using the registered {@link RestContext#REST_partParser part-parser} on the resource class.
* <li>
* The {@link RequestHeaders} object can also be passed as a parameter on the method.
* <li>
* The {@link Header @Header} annotation can be used to access individual header values.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.RestMethod.RequestHeaders}
* </ul>
*
* @return
* The headers on this request.
* <br>Never <jk>null</jk>.
*/
public RequestHeaders getHeaders() {
return headers;
}
@Override /* ServletRequest */
public String getHeader(String name) {
return getHeaders().getString(name);
}
@Override /* ServletRequest */
public Enumeration<String> getHeaders(String name) {
String[] v = headers.get(name);
if (v == null || v.length == 0)
return Collections.enumeration(Collections.EMPTY_LIST);
return Collections.enumeration(Arrays.asList(v));
}
/**
* Returns the media types that are valid for <c>Accept</c> headers on the request.
*
* @return The set of media types registered in the serializer group of this request.
*/
public List<MediaType> getProduces() {
return restJavaMethod == null ? Collections.<MediaType>emptyList() : restJavaMethod.supportedAcceptTypes;
}
/**
* Returns the media types that are valid for <c>Content-Type</c> headers on the request.
*
* @return The set of media types registered in the parser group of this request.
*/
public List<MediaType> getConsumes() {
return restJavaMethod == null ? Collections.<MediaType>emptyList() : restJavaMethod.supportedContentTypes;
}
/**
* Returns the {@link PropertyStore} for this request.
*
* <p>
* Consists of a read-only roll-up of all configuration properties defined on this method and class.
*
* @return
* The property store for this request.
* <br>Never <jk>null</jk>.
*/
public PropertyStore getPropertyStore() {
return restJavaMethod == null ? PropertyStore.DEFAULT : restJavaMethod.getPropertyStore();
}
/**
* Sets the charset to expect on the request body.
*/
@Override /* ServletRequest */
public void setCharacterEncoding(String charset) {
this.charset = charset;
}
/**
* Returns the charset specified on the <c>Content-Type</c> header, or <js>"UTF-8"</js> if not specified.
*/
@Override /* ServletRequest */
public String getCharacterEncoding() throws UnsupportedMediaType {
if (charset == null) {
// Determine charset
// NOTE: Don't use super.getCharacterEncoding() because the spec is implemented inconsistently.
// Jetty returns the default charset instead of null if the character is not specified on the request.
String h = getHeader("Content-Type");
if (h != null) {
int i = h.indexOf(";charset=");
if (i > 0)
charset = h.substring(i+9).trim();
}
if (charset == null && restJavaMethod != null)
charset = restJavaMethod.defaultCharset;
if (charset == null)
charset = "UTF-8";
if (! Charset.isSupported(charset))
throw new UnsupportedMediaType("Unsupported charset in header ''Content-Type'': ''{0}''", h);
}
return charset;
}
/**
* Wrapper around {@link #getCharacterEncoding()} that converts the value to a {@link Charset}.
*
* @return The request character encoding converted to a {@link Charset}.
*/
public Charset getCharset() {
String s = getCharacterEncoding();
return s == null ? null : Charset.forName(s);
}
@Override /* ServletRequest */
public Locale getLocale() {
String h = headers.getString("Accept-Language");
if (h != null) {
MediaTypeRange[] mr = MediaTypeRange.parse(h);
if (mr.length > 0)
return toLocale(mr[0].getMediaType().getType());
}
return super.getLocale();
}
@Override /* ServletRequest */
public Enumeration<Locale> getLocales() {
String h = headers.getString("Accept-Language");
if (h != null) {
MediaTypeRange[] mr = MediaTypeRange.parse(h);
if (mr.length > 0) {
List<Locale> l = new ArrayList<>(mr.length);
for (MediaTypeRange r : mr)
l.add(toLocale(r.getMediaType().getType()));
return enumeration(l);
}
}
return super.getLocales();
}
//-----------------------------------------------------------------------------------------------------------------
// Attributes
//-----------------------------------------------------------------------------------------------------------------
/**
* Request attributes.
*
* <p>
* Returns a {@link RequestAttributes} object that encapsulates access to attributes on the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public</jk> Object myMethod(RestRequest req) {
*
* <jc>// Get access to attributes.</jc>
* RequestAttributes a = req.getAttributes();
*
* <jc>// Get a header value as a POJO.</jc>
* UUID etag = a.get(<js>"ETag"</js>, UUID.<jk>class</jk>);
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* This object is modifiable.
* <li>
* Values are converted from strings using the registered {@link RestContext#REST_partParser part-parser} on the resource class.
* <li>
* The {@link RequestAttributes} object can also be passed as a parameter on the method.
* <li>
* The {@link Attr @Attr} annotation can be used to access individual attribute values.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.RestMethod.RequestAttributes}
* </ul>
*
* @return
* The headers on this request.
* <br>Never <jk>null</jk>.
*/
public RequestAttributes getAttributes() {
return attributes;
}
/**
* Same as {@link #getAttribute(String)} but returns a default value if not found.
*
* @param name The request attribute name.
* @param def The default value if the attribute doesn't exist.
* @return The request attribute value.
*/
public Object getAttribute(String name, Object def) {
Object o = super.getAttribute(name);
return (o == null ? def : o);
}
/**
* Shorthand method for calling {@link #setAttribute(String, Object)} fluently.
*
* @param name The request attribute name.
* @param value The request attribute value.
* @return This object (for method chaining).
*/
public RestRequest attr(String name, Object value) {
setAttribute(name, value);
return this;
}
//-----------------------------------------------------------------------------------------------------------------
// Query parameters
//-----------------------------------------------------------------------------------------------------------------
/**
* Query parameters.
*
* <p>
* Returns a {@link RequestQuery} object that encapsulates access to URL GET parameters.
*
* <p>
* Similar to {@link #getParameterMap()} but only looks for query parameters in the URL and not form posts.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public void</jk> doGet(RestRequest req) {
*
* <jc>// Get access to query parameters on the URL.</jc>
* RequestQuery q = req.getQuery();
*
* <jc>// Get query parameters converted to various types.</jc>
* <jk>int</jk> p1 = q.get(<js>"p1"</js>, 0, <jk>int</jk>.<jk>class</jk>);
* String p2 = q.get(<js>"p2"</js>, String.<jk>class</jk>);
* UUID p3 = q.get(<js>"p3"</js>, UUID.<jk>class</jk>);
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* This object is modifiable.
* <li>
* This method can be used to retrieve query parameters without triggering the underlying servlet API to load and parse the request body.
* <li>
* Values are converted from strings using the registered {@link RestContext#REST_partParser part-parser} on the resource class.
* <li>
* The {@link RequestQuery} object can also be passed as a parameter on the method.
* <li>
* The {@link Query @Query} annotation can be used to access individual query parameter values.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.RestMethod.RequestQuery}
* </ul>
*
* @return
* The query parameters as a modifiable map.
* <br>Never <jk>null</jk>.
*/
public RequestQuery getQuery() {
return queryParams;
}
/**
* Shortcut for calling <c>getQuery().getString(name)</c>.
*
* @param name The query parameter name.
* @return The query parameter value, or <jk>null</jk> if not found.
*/
public String getQuery(String name) {
return getQuery().getString(name);
}
//-----------------------------------------------------------------------------------------------------------------
// Form data parameters
//-----------------------------------------------------------------------------------------------------------------
/**
* Form-data.
*
* <p>
* Returns a {@link RequestFormData} object that encapsulates access to form post parameters.
*
* <p>
* Similar to {@link #getParameterMap()}, but only looks for form data in the HTTP body.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public void</jk> doPost(RestRequest req) {
*
* <jc>// Get access to parsed form data parameters.</jc>
* RequestFormData fd = req.getFormData();
*
* <jc>// Get form data parameters converted to various types.</jc>
* <jk>int</jk> p1 = fd.get(<js>"p1"</js>, 0, <jk>int</jk>.<jk>class</jk>);
* String p2 = fd.get(<js>"p2"</js>, String.<jk>class</jk>);
* UUID p3 = fd.get(<js>"p3"</js>, UUID.<jk>class</jk>);
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* This object is modifiable.
* <li>
* Values are converted from strings using the registered {@link RestContext#REST_partParser part-parser} on the resource class.
* <li>
* The {@link RequestFormData} object can also be passed as a parameter on the method.
* <li>
* The {@link FormData @FormDAta} annotation can be used to access individual form data parameter values.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.RestMethod.RequestFormData}
* </ul>
*
* @return
* The URL-encoded form data from the request.
* <br>Never <jk>null</jk>.
* @throws InternalServerError If query parameters could not be parsed.
* @see org.apache.juneau.http.annotation.FormData
*/
public RequestFormData getFormData() throws InternalServerError {
try {
if (formData == null) {
formData = new RequestFormData(this, restJavaMethod == null ? OpenApiParser.DEFAULT : restJavaMethod.partParser);
if (! body.isLoaded()) {
formData.putAll(getParameterMap());
} else {
Map<String,String[]> m = RestUtils.parseQuery(body.getReader());
for (Map.Entry<String,String[]> e : m.entrySet()) {
for (String v : e.getValue())
formData.put(e.getKey(), v);
}
}
}
formData.addDefault(restJavaMethod == null ? null : restJavaMethod.defaultFormData);
return formData;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
/**
* Shortcut for calling <c>getFormData().getString(name)</c>.
*
* @param name The form data parameter name.
* @return The form data parameter value, or <jk>null<jk> if not found.
*/
public String getFormData(String name) {
return getFormData().getString(name);
}
//-----------------------------------------------------------------------------------------------------------------
// Path parameters
//-----------------------------------------------------------------------------------------------------------------
/**
* Request path match.
*
* <p>
* Returns a {@link RequestPath} object that encapsulates access to everything related to the URL path.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(..., path=<js>"/{foo}/{bar}/{baz}/*"</js>)
* <jk>public void</jk> doGet(RestRequest req) {
*
* <jc>// Get access to path data.</jc>
* RequestPathMatch pm = req.getPathMatch();
*
* <jc>// Example URL: /123/qux/true/quux</jc>
*
* <jk>int</jk> foo = pm.getInt(<js>"foo"</js>); <jc>// =123</jc>
* String bar = pm.getString(<js>"bar"</js>); <jc>// =qux</jc>
* <jk>boolean</jk> baz = pm.getBoolean(<js>"baz"</js>); <jc>// =true</jc>
* String remainder = pm.getRemainder(); <jc>// =quux</jc>
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* This object is modifiable.
* <li>
* Values are converted from strings using the registered {@link RestContext#REST_partParser part-parser} on the resource class.
* <li>
* The {@link RequestPath} object can also be passed as a parameter on the method.
* <li>
* The {@link Path @Path} annotation can be used to access individual values.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.RestMethod.RequestPathMatch}
* </ul>
*
* @return
* The path data from the URL.
* <br>Never <jk>null</jk>.
*/
public RequestPath getPathMatch() {
return pathParams;
}
/**
* Shortcut for calling <c>getPathMatch().get(name)</c>.
*
* @param name The path variable name.
* @return The path variable value, or <jk>null</jk> if not found.
*/
public String getPath(String name) {
return getPathMatch().get(name);
}
/**
* Shortcut for calling <c>getPathMatch().getRemainder()</c>.
*
* @return The path remainder value, or <jk>null</jk> if not found.
*/
public String getPathRemainder() {
return getPathMatch().getRemainder();
}
//-----------------------------------------------------------------------------------------------------------------
// Body methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Request body.
*
* <p>
* Returns a {@link RequestBody} object that encapsulates access to the HTTP request body.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public void</jk> doPost2(RestRequest req) {
*
* <jc>// Convert body to a linked list of Person objects.</jc>
* List&lt;Person&gt; l = req.getBody().asType(LinkedList.<jk>class</jk>, Person.<jk>class</jk>);
* ..
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* The {@link RequestBody} object can also be passed as a parameter on the method.
* <li>
* The {@link Body @Body} annotation can be used to access the body as well.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.RestMethod.RequestBody}
* </ul>
*
* @return
* The body of this HTTP request.
* <br>Never <jk>null</jk>.
*/
public RequestBody getBody() {
return body;
}
/**
* Returns the HTTP body content as a {@link Reader}.
*
* <p>
* If {@code allowHeaderParams} init parameter is true, then first looks for {@code &body=xxx} in the URL query
* string.
*
* <p>
* Automatically handles GZipped input streams.
*/
@Override /* ServletRequest */
public BufferedReader getReader() throws IOException {
return getBody().getReader();
}
/**
* Returns the HTTP body content as an {@link InputStream}.
*
* <p>
* Automatically handles GZipped input streams.
*
* @return The negotiated input stream.
* @throws IOException If any error occurred while trying to get the input stream or wrap it in the GZIP wrapper.
*/
@Override /* ServletRequest */
public ServletInputStream getInputStream() throws IOException {
return getBody().getInputStream();
}
ServletInputStream getRawInputStream() throws IOException {
return inner.getInputStream();
}
//-----------------------------------------------------------------------------------------------------------------
// URI-related methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* HttpServletRequest */
public String getContextPath() {
String cp = context.getUriContext();
return cp == null ? super.getContextPath() : cp;
}
/**
* Returns the URI authority portion of the request.
*
* @return The URI authority portion of the request.
*/
public String getAuthorityPath() {
if (authorityPath == null)
authorityPath = context.getUriAuthority();
if (authorityPath == null) {
String scheme = getScheme();
int port = getServerPort();
StringBuilder sb = new StringBuilder(getScheme()).append("://").append(getServerName());
if (! (port == 80 && "http".equals(scheme) || port == 443 && "https".equals(scheme)))
sb.append(':').append(port);
authorityPath = sb.toString();
}
return authorityPath;
}
@Override /* HttpServletRequest */
public String getServletPath() {
String cp = context.getUriContext();
String sp = super.getServletPath();
return cp == null || ! sp.startsWith(cp) ? sp : sp.substring(cp.length());
}
/**
* Returns the URI context of the request.
*
* <p>
* The URI context contains all the information about the URI of the request, such as the servlet URI, context
* path, etc...
*
* @return The URI context of the request.
*/
public UriContext getUriContext() {
if (uriContext == null)
uriContext = new UriContext(getAuthorityPath(), getContextPath(), getServletPath(), StringUtils.urlEncodePath(super.getPathInfo()));
return uriContext;
}
/**
* Returns a URI resolver that can be used to convert URIs to absolute or root-relative form.
*
* @param resolution The URI resolution rule.
* @param relativity The relative URI relativity rule.
* @return The URI resolver for this request.
*/
public UriResolver getUriResolver(UriResolution resolution, UriRelativity relativity) {
return new UriResolver(resolution, relativity, getUriContext());
}
/**
* Shortcut for calling {@link #getUriResolver()} using {@link UriResolution#ROOT_RELATIVE} and
* {@link UriRelativity#RESOURCE}
*
* @return The URI resolver for this request.
*/
public UriResolver getUriResolver() {
return new UriResolver(context.getUriResolution(), context.getUriRelativity(), getUriContext());
}
/**
* Returns the URI for this request.
*
* <p>
* Similar to {@link #getRequestURI()} but returns the value as a {@link URI}.
* It also gives you the capability to override the query parameters (e.g. add new query parameters to the existing
* URI).
*
* @param includeQuery If <jk>true</jk> include the query parameters on the request.
* @param addQueryParams Augment the request URI with the specified query parameters.
* @return A new URI.
*/
public URI getUri(boolean includeQuery, Map<String,?> addQueryParams) {
String uri = getRequestURI();
if (includeQuery || addQueryParams != null) {
StringBuilder sb = new StringBuilder(uri);
RequestQuery rq = this.queryParams.copy();
if (addQueryParams != null)
for (Map.Entry<String,?> e : addQueryParams.entrySet())
rq.put(e.getKey(), e.getValue());
if (! rq.isEmpty())
sb.append('?').append(rq.toQueryString());
uri = sb.toString();
}
try {
return new URI(uri);
} catch (URISyntaxException e) {
// Shouldn't happen.
throw new RuntimeException(e);
}
}
//-----------------------------------------------------------------------------------------------------------------
// Labels
//-----------------------------------------------------------------------------------------------------------------
/**
* Resource information provider.
*
* <p>
* Returns a {@link RestInfoProvider} object that encapsulates all the textual meta-data on this resource such as
* descriptions, titles, and Swagger documentation.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public void</jk> doGet(RestRequest req) {
*
* <jc>// Get information provider.</jc>
* RestInfoProvider p = req.getInfoProvider();
*
* <jc>// Get localized strings.</jc>
* String resourceTitle = p.getTitle(req);
* String methodDescription = p.getMethodDescription(req.getMethod(), req);
* Contact contact = p.getContact(req);
* ..
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* The {@link RestInfoProvider} object can also be passed as a parameter on the method.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='jf'>{@link org.apache.juneau.rest.RestContext#REST_infoProvider}
* <li class='jic'>{@link org.apache.juneau.rest.RestInfoProvider}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getSiteName()}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getResourceTitle()}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getResourceDescription()}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getMethodSummary()}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getMethodDescription()}
* <li class='link'>{@doc juneau-rest-server.Swagger}
* </ul>
*
* @return
* The info provider on the resource.
* <br>Never <jk>null</jk>.
*/
public RestInfoProvider getInfoProvider() {
return context.getInfoProvider();
}
/**
* Returns the localized swagger associated with the resource.
*
* <p>
* A shortcut for calling <c>getInfoProvider().getSwagger(request);</c>
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public</jk> List&lt;Tag&gt; getSwaggerTags(RestRequest req) {
* <jk>return</jk> req.getSwagger().getTags();
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* The {@link Swagger} object can also be passed as a parameter on the method.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='jf'>{@link org.apache.juneau.rest.RestContext#REST_infoProvider}
* <li class='jic'>{@link org.apache.juneau.rest.RestInfoProvider}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getInfoProvider()}
* <li class='link'>{@doc juneau-rest-server.Swagger}
* </ul>
*
* @return
* The swagger associated with the resource.
* <br>Never <jk>null</jk>.
*/
public Swagger getSwagger() {
try {
if (swagger == null)
swagger = context.getInfoProvider().getSwagger(this);
return swagger;
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
/**
* Returns the localized site name.
*
* <p>
* The site name is intended to be a title that can be applied to the entire site.
*
* <p>
* One possible use is if you want to add the same title to the top of all pages by defining a header on a
* common parent class like so:
* <p class='bcode w800'>
* htmldoc=<ja>@HtmlDoc</ja>(
* header={
* <js>"&lt;h1&gt;$R{siteName}&lt;/h1&gt;"</js>,
* <js>"&lt;h2&gt;$R{resourceTitle}&lt;/h2&gt;"</js>
* }
* )
* </p>
*
* <p>
* Equivalent to calling {@link RestInfoProvider#getSiteName(RestRequest)} with this object.
*
* @return The localized site name.
*/
public String getSiteName() {
try {
return context.getInfoProvider().getSiteName(this);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
/**
* Returns the localized resource title.
*
* <p>
* Equivalent to calling {@link RestInfoProvider#getTitle(RestRequest)} with this object.
*
* @return The localized resource title.
*/
public String getResourceTitle() {
try {
return context.getInfoProvider().getTitle(this);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
/**
* Returns the localized resource description.
*
* <p>
* Equivalent to calling {@link RestInfoProvider#getDescription(RestRequest)} with this object.
*
* @return The localized resource description.
*/
public String getResourceDescription() {
try {
return context.getInfoProvider().getDescription(this);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
/**
* Returns the localized method summary.
*
* <p>
* Equivalent to calling {@link RestInfoProvider#getMethodSummary(Method, RestRequest)} with this object.
*
* @return The localized method description.
*/
public String getMethodSummary() {
try {
return context.getInfoProvider().getMethodSummary(javaMethod, this);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
/**
* Returns the localized method description.
*
* <p>
* Equivalent to calling {@link RestInfoProvider#getMethodDescription(Method, RestRequest)} with this object.
*
* @return The localized method description.
*/
public String getMethodDescription() {
try {
return context.getInfoProvider().getMethodDescription(javaMethod, this);
} catch (RestException e) {
throw e;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the serializers associated with this request.
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.Serializers}
* </ul>
*
* @return The serializers associated with this request.
*/
public SerializerGroup getSerializers() {
return restJavaMethod == null ? SerializerGroup.EMPTY : restJavaMethod.serializers;
}
/**
* Returns the parsers associated with this request.
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.Parsers}
* </ul>
*
* @return The parsers associated with this request.
*/
public ParserGroup getParsers() {
return restJavaMethod == null ? ParserGroup.EMPTY : restJavaMethod.parsers;
}
/**
* Returns the part serializer associated with this request.
*
* @return The part serializer associated with this request.
*/
public HttpPartParser getPartParser() {
return restJavaMethod == null ? OpenApiParser.DEFAULT : restJavaMethod.partParser;
}
/**
* Returns the part serializer associated with this request.
*
* @return The part serializer associated with this request.
*/
public HttpPartSerializer getPartSerializer() {
return restJavaMethod == null ? OpenApiSerializer.DEFAULT : restJavaMethod.partSerializer;
}
/**
* Returns the method of this request.
*
* <p>
* If <c>allowHeaderParams</c> init parameter is <jk>true</jk>, then first looks for
* <c>&amp;method=xxx</c> in the URL query string.
*/
@Override /* ServletRequest */
public String getMethod() {
return method;
}
/**
* Returns the HTTP 1.1 method name of the request as an enum.
*
* <p>
* Note that non-RFC2616 method names resolve as {@link HttpMethod#OTHER}.
*
* @return The HTTP method.
*/
public HttpMethod getHttpMethod() {
return HttpMethod.forString(method);
}
@Override /* ServletRequest */
public int getContentLength() {
return getBody().getContentLength();
}
int getRawContentLength() {
return super.getContentLength();
}
/**
* Returns <jk>true</jk> if <c>&amp;plainText=true</c> was specified as a URL parameter.
*
* <p>
* This indicates that the <c>Content-Type</c> of the output should always be set to <js>"text/plain"</js>
* to make it easy to render in a browser.
*
* <p>
* This feature is useful for debugging.
*
* @return <jk>true</jk> if {@code &amp;plainText=true} was specified as a URL parameter
*/
public boolean isPlainText() {
return "true".equals(getQuery().getString("plainText", "false"));
}
/**
* Returns the resource bundle for the request locale.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public</jk> String sayHello(RestRequest req, <ja>@Query</ja>(<js>"user"</js>) String user) {
*
* <jc>// Get message bundle.</jc>
* MessageBundle mb = req.getMessageBundle();
*
* <jc>// Return a localized message.</jc>
* <jk>return</jk> mb.getString(<js>"hello.message"</js>, user);
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* The {@link MessageBundle} object can also be passed as a parameter on the method.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='jf'>{@link org.apache.juneau.rest.RestContext#REST_messages}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getMessage(String,Object...)}
* <li class='link'>{@doc juneau-rest-server.Messages}
* </ul>
*
* @return
* The resource bundle.
* <br>Never <jk>null</jk>.
*/
public MessageBundle getMessageBundle() {
return context.getMessages().getBundle(getLocale());
}
/**
* Shortcut method for calling {@link MessageBundle#getString(Locale, String, Object...)} based on the request locale.
*
* @param key The message key.
* @param args Optional {@link MessageFormat}-style arguments.
* @return The localized message.
*/
public String getMessage(String key, Object...args) {
return context.getMessages().getString(getLocale(), key, args);
}
/**
* Returns the resource context handling the request.
*
* <p>
* Can be used to access servlet-init parameters or annotations during requests, such as in calls to
* {@link RestGuard#guard(RestRequest, RestResponse)}..
*
* @return The resource context handling the request.
*/
public RestContext getContext() {
return context;
}
/**
* Returns the java method handling the request.
*
* <p>
* Can be used to access the method name or method annotations during requests, such as in calls to
* {@link RestGuard#guard(RestRequest, RestResponse)}.
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* This returns <jk>null</jk> when evaluating servlet-level guards since the method has not been resolved at that
* point of execution.
* </ul>
*
* @return The Java method handling the request, or <c>null</c> if the method has not yet been resolved.
*/
public Method getJavaMethod() {
return javaMethod;
}
/**
* Returns the {@link BeanSession} associated with this request.
*
* @return The request bean session.
*/
public BeanSession getBeanSession() {
return beanSession;
}
/**
* Returns <jk>true</jk> if debug mode is enabled.
*
* Debug mode is enabled by simply adding <js>"?debug=true"</js> to the query string or adding a <c>Debug: true</c> header on the request.
*
* @return <jk>true</jk> if debug mode is enabled.
*/
public boolean isDebug() {
return debug;
}
/**
* Request-level variable resolver session.
*
* <p>
* Used to resolve SVL variables in text.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public</jk> String sayHello(RestRequest req) {
*
* <jc>// Get var resolver session.</jc>
* VarResolverSession session = getVarResolverSession();
*
* <jc>// Use it to construct a customized message from a query parameter.</jc>
* <jk>return</jk> session.resolve(<js>"Hello $RQ{user}!"</js>);
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* The {@link VarResolverSession} object can also be passed as a parameter on the method.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='jm'>{@link org.apache.juneau.rest.RestContext#getVarResolver()}
* <li class='link'>{@doc juneau-rest-server.SvlVariables}
* </ul>
*
* @return The variable resolver for this request.
*/
public VarResolverSession getVarResolverSession() {
if (varSession == null)
varSession = context
.getVarResolver()
.createSession(context.getCallHandler().getSessionObjects(this, context.getResponse()))
.sessionObject("req", this)
.sessionObject("res", res);
return varSession;
}
/**
* Returns an instance of a {@link ReaderResource} that represents the contents of a resource text file from the
* classpath.
*
* <p>
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// A rest method that (unsafely!) returns the contents of a localized file </jc>
* <jc>// from the classpath and resolves any SVL variables embedded in it.</jc>
* <ja>@RestMethod</ja>(...)
* <jk>public</jk> String myMethod(RestRequest req, <ja>@Query</ja>(<js>"file"</js>) String file) {
* <jk>return</jk> req.getClasspathResourceAsString(file, <jk>true</jk>);
* }
* </p>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='jf'>{@link org.apache.juneau.rest.RestContext#REST_classpathResourceFinder}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getClasspathReaderResource(String, boolean)}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getClasspathReaderResource(String)}
* </ul>
*
* @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
* @param resolveVars
* If <jk>true</jk>, any SVL variables will be
* resolved by the variable resolver returned by {@link #getVarResolverSession()}.
* <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
* @param mediaType The value to set as the <js>"Content-Type"</js> header for this object.
* @param cached If <jk>true</jk>, the resource will be read into a byte array for fast serialization.
* @return A new reader resource, or <jk>null</jk> if resource could not be found.
* @throws IOException Thrown by underlying stream.
*/
public ReaderResource getClasspathReaderResource(String name, boolean resolveVars, MediaType mediaType, boolean cached) throws IOException {
String s = context.getClasspathResourceAsString(name, getLocale());
if (s == null)
return null;
ResolvingReaderResource.Builder b = ResolvingReaderResource.create().mediaType(mediaType).contents(s);
if (resolveVars)
b.varResolver(getVarResolverSession());
if (cached)
b.cached();
return b.build();
}
/**
* Same as {@link #getClasspathReaderResource(String, boolean, MediaType, boolean)} except uses the resource mime-type map
* constructed using {@link RestContextBuilder#mimeTypes(String...)} to determine the media type.
*
* @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
* @param resolveVars
* If <jk>true</jk>, any SVL variables will be
* resolved by the variable resolver returned by {@link #getVarResolverSession()}.
* <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
* @return A new reader resource, or <jk>null</jk> if resource could not be found.
* @throws IOException Thrown by underlying stream.
*/
public ReaderResource getClasspathReaderResource(String name, boolean resolveVars) throws IOException {
return getClasspathReaderResource(name, resolveVars, MediaType.forString(context.getMediaTypeForName(name)), false);
}
/**
* Same as {@link #getClasspathReaderResource(String, boolean)} with <code>resolveVars == <jk>false</jk></code>
*
* @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
* @return A new reader resource, or <jk>null</jk> if resource could not be found.
* @throws IOException Thrown by underlying stream.
*/
public ReaderResource getClasspathReaderResource(String name) throws IOException {
return getClasspathReaderResource(name, false, MediaType.forString(context.getMediaTypeForName(name)), false);
}
/**
* Returns an instance of a {@link StreamResource} that represents the contents of a resource binary file from the
* classpath.
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='jf'>{@link org.apache.juneau.rest.RestContext#REST_classpathResourceFinder}
* <li class='jm'>{@link org.apache.juneau.rest.RestRequest#getClasspathStreamResource(String)}
* </ul>
*
* @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
* @param mediaType The value to set as the <js>"Content-Type"</js> header for this object.
* @param cached If <jk>true</jk>, the resource will be read into a byte array for fast serialization.
* @return A new stream resource, or <jk>null</jk> if resource could not be found.
* @throws IOException Thrown by underlying stream.
*/
@SuppressWarnings("resource")
public StreamResource getClasspathStreamResource(String name, MediaType mediaType, boolean cached) throws IOException {
InputStream is = context.getClasspathResource(name, getLocale());
if (is == null)
return null;
StreamResource.Builder b = StreamResource.create().mediaType(mediaType).contents(is);
if (cached)
b.cached();
return b.build();
}
/**
* Same as {@link #getClasspathStreamResource(String, MediaType, boolean)} except uses the resource mime-type map
* constructed using {@link RestContextBuilder#mimeTypes(String...)} to determine the media type.
*
* @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
* @return A new stream resource, or <jk>null</jk> if resource could not be found.
* @throws IOException Thrown by underlying stream.
*/
public StreamResource getClasspathStreamResource(String name) throws IOException {
return getClasspathStreamResource(name, MediaType.forString(context.getMediaTypeForName(name)), false);
}
/**
* Config file associated with the resource.
*
* <p>
* Returns a config file with session-level variable resolution.
*
* The config file is identified via one of the following:
* <ul>
* <li class='ja'>{@link RestResource#config()}
* <li class='jm'>{@link RestContextBuilder#config(Config)}
* </ul>
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public void</jk> doGet(RestRequest req) {
*
* <jc>// Get config file.</jc>
* Config cf = req.getConfig();
*
* <jc>// Get simple values from config file.</jc>
* <jk>int</jk> timeout = cf.getInt(<js>"MyResource/timeout"</js>, 10000);
*
* <jc>// Get complex values from config file.</jc>
* MyBean b = cf.getObject(<js>"MyResource/myBean"</js>, MyBean.<jk>class</jk>);
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* The {@link Config} object can also be passed as a parameter on the method.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='link'>{@doc juneau-rest-server.ConfigurationFiles}
* </ul>
*
* @return
* The config file associated with the resource, or <jk>null</jk> if resource does not have a config file
* associated with it.
*/
public Config getConfig() {
if (cf == null)
cf = context.getConfig().resolving(getVarResolverSession());
return cf;
}
/**
* Returns the widgets used for resolving <js>"$W{...}"</js> string variables.
*
* @return
* The widgets used for resolving <js>"$W{...}"</js> string variables.
* Never <jk>null</jk>.
*
* @deprecated No replacement.
*/
@Deprecated
public Map<String,Widget> getWidgets() {
return restJavaMethod == null ? Collections.<String,Widget>emptyMap() : restJavaMethod.widgets;
}
/**
* Creates a proxy interface to retrieve HTTP parts of this request as a proxy bean.
*
* <h5 class='section'>Examples:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(path=<js>"/mypath/{p1}/{p2}/*"</js>)
* <jk>public void</jk> myMethod(@Request MyRequest rb) {...}
*
* <jk>public interface</jk> MyRequest {
*
* <ja>@Path</ja> <jc>// Path variable name inferred from getter.</jc>
* String getP1();
*
* <ja>@Path</ja>(<js>"p2"</js>)
* String getX();
*
* <ja>@Path</ja>(<js>"/*"</js>)
* String getRemainder();
*
* <ja>@Query</ja>
* String getQ1();
*
* <jc>// Schema-based query parameter: Pipe-delimited lists of comma-delimited lists of integers.</jc>
* <ja>@Query</ja>(
* collectionFormat=<js>"pipes"</js>
* items=<ja>@Items</ja>(
* items=<ja>@SubItems</ja>(
* collectionFormat=<js>"csv"</js>
* type=<js>"integer"</js>
* )
* )
* )
* <jk>int</jk>[][] getQ3();
*
* <ja>@Header</ja>(<js>"*"</js>)
* Map&lt;String,Object&gt; getHeaders();
* </p>
*
* @param c The request bean interface to instantiate.
* @return A new request bean proxy for this REST request.
*/
public <T> T getRequest(Class<T> c) {
return getRequest(RequestBeanMeta.create(c, getContext().getPropertyStore()));
}
/**
* Same as {@link #getRequest(Class)} but used on pre-instantiated {@link RequestBeanMeta} objects.
*
* @param rbm The metadata about the request bean interface to create.
* @return A new request bean proxy for this REST request.
*/
public <T> T getRequest(final RequestBeanMeta rbm) {
try {
Class<T> c = (Class<T>)rbm.getClassMeta().getInnerClass();
final BeanMeta<T> bm = getBeanSession().getBeanMeta(c);
return (T)Proxy.newProxyInstance(
c.getClassLoader(),
new Class[] { c },
new InvocationHandler() {
@Override /* InvocationHandler */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RequestBeanPropertyMeta pm = rbm.getProperty(method.getName());
if (pm != null) {
HttpPartParser pp = pm.getParser(getPartParser());
HttpPartSchema schema = pm.getSchema();
String name = pm.getPartName();
ClassMeta<?> type = getContext().getClassMeta(method.getGenericReturnType());
HttpPartType pt = pm.getPartType();
if (pt == HttpPartType.BODY)
return getBody().schema(schema).asType(type);
if (pt == QUERY)
return getQuery().get(pp, schema, name, type);
if (pt == FORMDATA)
return getFormData().get(pp, schema, name, type);
if (pt == HEADER)
return getHeaders().get(pp, schema, name, type);
if (pt == PATH)
return getPathMatch().get(pp, schema, name, type);
}
return null;
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override /* Object */
public String toString() {
StringBuilder sb = new StringBuilder("\n").append(getDescription()).append("\n");
sb.append("---Headers---\n");
for (Enumeration<String> e = getHeaderNames(); e.hasMoreElements();) {
String h = e.nextElement();
sb.append("\t").append(h).append(": ").append(getHeader(h)).append("\n");
}
sb.append("---Default Servlet Headers---\n");
for (Map.Entry<String,Object> e : context.getDefaultRequestHeaders().entrySet()) {
sb.append("\t").append(e.getKey()).append(": ").append(e.getValue()).append("\n");
}
if (javaMethod == null) {
sb.append("***init() not called yet!***\n");
} else if (method.equals("PUT") || method.equals("POST")) {
try {
sb.append("---Body UTF-8---\n");
sb.append(body.asString()).append("\n");
sb.append("---Body Hex---\n");
sb.append(body.asSpacedHex()).append("\n");
} catch (Exception e1) {
sb.append(e1.getLocalizedMessage());
context.getLogger().log(WARNING, e1, "Error occurred while trying to read debug input.");
}
}
return sb.toString();
}
/**
* Returns the session arguments to pass to serializers.
*
* @return The session arguments to pass to serializers.
*/
public SerializerSessionArgs getSerializerSessionArgs() {
if (serializerSessionArgs == null)
serializerSessionArgs = SerializerSessionArgs
.create()
.properties(getProperties())
.javaMethod(getJavaMethod())
.locale(getLocale())
.timeZone(getHeaders().getTimeZone())
.debug(isDebug() ? true : null)
.uriContext(getUriContext())
.resolver(getVarResolverSession())
.useWhitespace(isPlainText() ? true : null);
return serializerSessionArgs;
}
/**
* Returns the session arguments to pass to parsers.
*
* @return The session arguments to pass to parsers.
*/
public ParserSessionArgs getParserSessionArgs() {
if (parserSessionArgs == null)
parserSessionArgs =
ParserSessionArgs
.create()
.properties(getProperties())
.javaMethod(getJavaMethod())
.locale(getLocale())
.timeZone(getHeaders().getTimeZone())
.debug(isDebug() ? true : null);
return parserSessionArgs;
}
/**
* Logger.
*
* <p>
* Shortcut for calling <c>getContext().getLogger()</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <ja>@RestMethod</ja>(...)
* <jk>public void</jk> doGet(RestRequest req) {
*
* req.getLogger().logObjects(<jsf>FINE</jsf>, <js>"Request query parameters = {0}"</js>, req.getQuery());
* }
* </p>
*
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
* <li>
* The {@link RestLogger} object can also be passed as a parameter on the method.
* </ul>
*
* <h5 class='section'>See Also:</h5>
* <ul>
* <li class='jf'>{@link org.apache.juneau.rest.RestContext#REST_logger}
* <li class='jac'>{@link org.apache.juneau.rest.RestLogger}
* <li class='jm'>{@link org.apache.juneau.rest.RestServlet#log(Level, String, Object...)}
* <li class='jm'>{@link org.apache.juneau.rest.RestServlet#logObjects(Level, String, Object...)}
* <li class='link'>{@doc juneau-rest-server.LoggingAndErrorHandling}
* </ul>
*
* @return
* The logger associated with the resource context.
* <br>Never <jk>null</jk>.
*/
public RestLogger getLogger() {
return context.getLogger();
}
void close() {
if (cf != null) {
try {
cf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Returns metadata about the specified response object if it's annotated with {@link Response @Response}.
*
* @param o The response POJO.
* @return Metadata about the specified response object, or <jk>null</jk> if it's not annotated with {@link Response @Response}.
*/
public ResponseBeanMeta getResponseBeanMeta(Object o) {
return restJavaMethod == null ? null : restJavaMethod.getResponseBeanMeta(o);
}
/**
* Returns metadata about the specified response object if it's annotated with {@link ResponseHeader @ResponseHeader}.
*
* @param o The response POJO.
* @return Metadata about the specified response object, or <jk>null</jk> if it's not annotated with {@link ResponseHeader @ResponseHeader}.
*/
public ResponsePartMeta getResponseHeaderMeta(Object o) {
return restJavaMethod == null ? null : restJavaMethod.getResponseHeaderMeta(o);
}
/**
* Returns metadata about the specified response object if it's annotated with {@link ResponseBody @ResponseBody}.
*
* @param o The response POJO.
* @return Metadata about the specified response object, or <jk>null</jk> if it's not annotated with {@link ResponseBody @ResponseBody}.
*/
public ResponsePartMeta getResponseBodyMeta(Object o) {
return restJavaMethod == null ? null : restJavaMethod.getResponseBodyMeta(o);
}
/**
* Returns the schema generator with settings assigned on this method and class.
*
* @return The schema generator.
*/
public JsonSchemaGenerator getJsonSchemaGenerator() {
return restJavaMethod == null ? context.getJsonSchemaGenerator() : restJavaMethod.getJsonSchemaGenerator();
}
/**
* Returns the wrapped servlet request.
*
* @return The wrapped servlet request.
*/
protected HttpServletRequest getInner() {
return inner;
}
//-----------------------------------------------------------------------------------------------------------------
// Utility methods
//-----------------------------------------------------------------------------------------------------------------
/*
* Converts an Accept-Language value entry to a Locale.
*/
private static Locale toLocale(String lang) {
String country = "";
int i = lang.indexOf('-');
if (i > -1) {
country = lang.substring(i+1).trim();
lang = lang.substring(0,i).trim();
}
return new Locale(lang, country);
}
void setJavaMethod(Method method) {
this.javaMethod = method;
}
}