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

org.apache.juneau.rest.httppart.RequestContent 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.httppart;

import static org.apache.juneau.common.internal.IOUtils.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import jakarta.servlet.*;

import org.apache.juneau.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.encoders.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.marshaller.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.http.header.*;
import org.apache.juneau.http.response.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.util.*;

/**
 * Contains the content of the HTTP request.
 *
 * 

* The {@link RequestContent} object is the API for accessing the content of an HTTP request. * It can be accessed by passing it as a parameter on your REST Java method: *

*

* @RestPost(...) * public Object myMethod(RequestContent content) {...} *

* *
Example:
*

* @RestPost(...) * public void doPost(RequestContent content) { * // Convert content to a linked list of Person objects. * List<Person> list = content.as(LinkedList.class, Person.class); * ... * } *

* *

* Some important methods on this class are: *

*
    *
  • {@link RequestContent} *
      *
    • Methods for accessing the raw contents of the request content: *
        *
      • {@link RequestContent#asBytes() asBytes()} *
      • {@link RequestContent#asHex() asHex()} *
      • {@link RequestContent#asSpacedHex() asSpacedHex()} *
      • {@link RequestContent#asString() asString()} *
      • {@link RequestContent#getInputStream() getInputStream()} *
      • {@link RequestContent#getReader() getReader()} *
      *
    • Methods for parsing the contents of the request content: *
        *
      • {@link RequestContent#as(Class) as(Class)} *
      • {@link RequestContent#as(Type, Type...) as(Type, Type...)} *
      • {@link RequestContent#setSchema(HttpPartSchema) setSchema(HttpPartSchema)} *
      *
    • Other methods: *
        *
      • {@link RequestContent#cache() cache()} *
      • {@link RequestContent#getParserMatch() getParserMatch()} *
      *
    *
* *
See Also:
*/ @SuppressWarnings("unchecked") public class RequestContent { private byte[] content; private final RestRequest req; private EncoderSet encoders; private Encoder encoder; private ParserSet parsers; private long maxInput; private int contentLength = 0; private MediaType mediaType; private Parser parser; private HttpPartSchema schema; /** * Constructor. * * @param req The request creating this bean. */ public RequestContent(RestRequest req) { this.req = req; } /** * Sets the encoders to use for decoding this content. * * @param value The new value for this setting. * @return This object. */ public RequestContent encoders(EncoderSet value) { this.encoders = value; return this; } /** * Sets the parsers to use for parsing this content. * * @param value The new value for this setting. * @return This object. */ public RequestContent parsers(ParserSet value) { this.parsers = value; return this; } /** * Sets the schema for this content. * * @param schema The new schema for this content. * @return This object. */ public RequestContent setSchema(HttpPartSchema schema) { this.schema = schema; return this; } /** * Sets the max input value for this content. * * @param value The new value for this setting. * @return This object. */ public RequestContent maxInput(long value) { this.maxInput = value; return this; } /** * Sets the media type of this content. * * @param value The new value for this setting. * @return This object. */ public RequestContent mediaType(MediaType value) { this.mediaType = value; return this; } /** * Sets the parser to use for this content. * * @param value The new value for this setting. * @return This object. */ public RequestContent parser(Parser value) { this.parser = value; return this; } /** * Sets the contents of this content. * * @param value The new value for this setting. * @return This object. */ public RequestContent content(byte[] value) { this.content = value; return this; } boolean isLoaded() { return content != null; } /** * Reads the input from the HTTP request parsed into a POJO. * *

* The parser used is determined by the matching Content-Type header on the request. * *

* If type is null or Object.class, then the actual type will be determined * automatically based on the following input: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
TypeJSON inputXML inputReturn type
object"{...}"<object>...</object>
<x type='object'>...</x>
{@link JsonMap}
array"[...]"<array>...</array>
<x type='array'>...</x>
{@link JsonList}
string"'...'"<string>...</string>
<x type='string'>...</x>
{@link String}
number123<number>123</number>
<x type='number'>...</x>
{@link Number}
booleantrue<boolean>true</boolean>
<x type='boolean'>...</x>
{@link Boolean}
nullnull or blank<null/> or blank
<x type='null'/>
null
* *

* Refer to POJO Categories for a complete definition of supported POJOs. * *

Examples:
*

* // Parse into an integer. * int content1 = req.getContent().as(int.class); * * // Parse into an int array. * int[] content2 = req.getContent().as(int[].class); * // Parse into a bean. * MyBean content3 = req.getContent().as(MyBean.class); * * // Parse into a linked-list of objects. * List content4 = req.getContent().as(LinkedList.class); * * // Parse into a map of object keys/values. * Map content5 = req.getContent().as(TreeMap.class); *

* *
Notes:
    *
  • * If {@code allowContentParam} init parameter is true, then first looks for {@code &content=xxx} in the URL query string. *
* * @param type The class type to instantiate. * @param The class type to instantiate. * @return The input parsed to a POJO. * @throws BadRequest Thrown if input could not be parsed or fails schema validation. * @throws UnsupportedMediaType Thrown if the Content-Type header value is not supported by one of the parsers. * @throws InternalServerError Thrown if an {@link IOException} occurs. */ public T as(Class type) throws BadRequest, UnsupportedMediaType, InternalServerError { return getInner(getClassMeta(type)); } /** * Reads the input from the HTTP request parsed into a POJO. * *

* This is similar to {@link #as(Class)} but allows for complex collections of POJOs to be created. * *

Examples:
*

* // Parse into a linked-list of strings. * List<String> content1 = req.getContent().as(LinkedList.class, String.class); * * // Parse into a linked-list of linked-lists of strings. * List<List<String>> content2 = req.getContent().as(LinkedList.class, LinkedList.class, String.class); * * // Parse into a map of string keys/values. * Map<String,String> content3 = req.getContent().as(TreeMap.class, String.class, String.class); * * // Parse into a map containing string keys and values of lists containing beans. * Map<String,List<MyBean>> content4 = req.getContent().as(TreeMap.class, String.class, List.class, MyBean.class); *

* *
Notes:
    *
  • * Collections must be followed by zero or one parameter representing the value type. *
  • * Maps must be followed by zero or two parameters representing the key and value types. *
  • * If {@code allowContentParam} init parameter is true, then first looks for {@code &content=xxx} in the URL query string. *
* * @param type * The type of object to create. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} * @param args * The type arguments of the class if it's a collection or map. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} *
Ignored if the main type is not a map or collection. * @param The class type to instantiate. * @return The input parsed to a POJO. * @throws BadRequest Thrown if input could not be parsed or fails schema validation. * @throws UnsupportedMediaType Thrown if the Content-Type header value is not supported by one of the parsers. * @throws InternalServerError Thrown if an {@link IOException} occurs. */ public T as(Type type, Type...args) throws BadRequest, UnsupportedMediaType, InternalServerError { return getInner(this.getClassMeta(type, args)); } /** * Returns the HTTP content content as a plain string. * *
Notes:
    *
  • * If {@code allowContentParam} init parameter is true, then first looks for {@code &content=xxx} in the URL query string. *
* * @return The incoming input from the connection as a plain string. * @throws IOException If a problem occurred trying to read from the reader. */ public String asString() throws IOException { cache(); return new String(content, UTF8); } /** * Returns the HTTP content content as a plain string. * *
Notes:
    *
  • * If {@code allowContentParam} init parameter is true, then first looks for {@code &content=xxx} in the URL query string. *
* * @return The incoming input from the connection as a plain string. * @throws IOException If a problem occurred trying to read from the reader. */ public byte[] asBytes() throws IOException { cache(); return content; } /** * Returns the HTTP content content as a simple hexadecimal character string. * *
Example:
*

* 0123456789ABCDEF *

* * @return The incoming input from the connection as a plain string. * @throws IOException If a problem occurred trying to read from the reader. */ public String asHex() throws IOException { cache(); return toHex(content); } /** * Returns the HTTP content content as a simple space-delimited hexadecimal character string. * *
Example:
*

* 01 23 45 67 89 AB CD EF *

* * @return The incoming input from the connection as a plain string. * @throws IOException If a problem occurred trying to read from the reader. */ public String asSpacedHex() throws IOException { cache(); return toSpacedHex(content); } /** * Returns the HTTP content content as a {@link Reader}. * *
Notes:
    *
  • * If {@code allowContentParam} init parameter is true, then first looks for {@code &content=xxx} in the URL query string. *
  • * Automatically handles GZipped input streams. *
* * @return The content contents as a reader. * @throws IOException Thrown by underlying stream. */ public BufferedReader getReader() throws IOException { Reader r = getUnbufferedReader(); if (r instanceof BufferedReader) return (BufferedReader)r; int len = req.getHttpServletRequest().getContentLength(); int buffSize = len <= 0 ? 8192 : Math.max(len, 8192); return new BufferedReader(r, buffSize); } /** * Same as {@link #getReader()}, but doesn't encapsulate the result in a {@link BufferedReader}; * * @return An unbuffered reader. * @throws IOException Thrown by underlying stream. */ protected Reader getUnbufferedReader() throws IOException { if (content != null) return new CharSequenceReader(new String(content, UTF8)); return new InputStreamReader(getInputStream(), req.getCharset()); } /** * Returns the HTTP content content as an {@link InputStream}. * * @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. */ public ServletInputStream getInputStream() throws IOException { if (content != null) return new BoundedServletInputStream(content); Encoder enc = getEncoder(); InputStream is = req.getHttpServletRequest().getInputStream(); if (enc == null) return new BoundedServletInputStream(is, maxInput); return new BoundedServletInputStream(enc.getInputStream(is), maxInput); } /** * Returns the parser and media type matching the request Content-Type header. * * @return * The parser matching the request Content-Type header, or {@link Optional#empty()} if no matching parser was * found. * Includes the matching media type. */ public Optional getParserMatch() { if (mediaType != null && parser != null) return optional(new ParserMatch(mediaType, parser)); MediaType mt = getMediaType(); return optional(mt).map(x -> parsers.getParserMatch(x)); } private MediaType getMediaType() { if (mediaType != null) return mediaType; Optional ct = req.getHeader(ContentType.class); if (!ct.isPresent() && content != null) return MediaType.UON; return ct.isPresent() ? ct.get().asMediaType().orElse(null) : null; } private T getInner(ClassMeta cm) throws BadRequest, UnsupportedMediaType, InternalServerError { try { return parse(cm); } catch (UnsupportedMediaType e) { throw e; } catch (SchemaValidationException e) { throw new BadRequest("Validation failed on request content. " + e.getLocalizedMessage()); } catch (ParseException e) { throw new BadRequest(e, "Could not convert request content content to class type ''{0}''.", cm); } catch (IOException e) { throw new InternalServerError(e, "I/O exception occurred while parsing request content."); } catch (Exception e) { throw new InternalServerError(e, "Exception occurred while parsing request content."); } } /* Workhorse method */ private T parse(ClassMeta cm) throws SchemaValidationException, ParseException, UnsupportedMediaType, IOException { if (cm.isReader()) return (T)getReader(); if (cm.isInputStream()) return (T)getInputStream(); Optional timeZone = req.getTimeZone(); Locale locale = req.getLocale(); ParserMatch pm = getParserMatch().orElse(null); if (schema == null) schema = HttpPartSchema.DEFAULT; if (pm != null) { Parser p = pm.getParser(); MediaType mediaType = pm.getMediaType(); ParserSession session = p .createSession() .properties(req.getAttributes().asMap()) .javaMethod(req.getOpContext().getJavaMethod()) .locale(locale) .timeZone(timeZone.orElse(null)) .mediaType(mediaType) .apply(ReaderParser.Builder.class, x -> x.streamCharset(req.getCharset())) .schema(schema) .debug(req.isDebug() ? true : null) .outer(req.getContext().getResource()) .build(); try (Closeable in = session.isReaderParser() ? getUnbufferedReader() : getInputStream()) { T o = session.parse(in, cm); if (schema != null) schema.validateOutput(o, cm.getBeanContext()); return o; } } if (cm.hasReaderMutater()) return cm.getReaderMutater().mutate(getReader()); if (cm.hasInputStreamMutater()) return cm.getInputStreamMutater().mutate(getInputStream()); MediaType mt = getMediaType(); if ((isEmpty(stringify(mt)) || mt.toString().startsWith("text/plain")) && cm.hasStringMutater()) return cm.getStringMutater().mutate(asString()); Optional ct = req.getHeader(ContentType.class); throw new UnsupportedMediaType( "Unsupported media-type in request header ''Content-Type'': ''{0}''\n\tSupported media-types: {1}", ct.isPresent() ? ct.get().asMediaType().orElse(null) : "not-specified", Json5.of(req.getOpContext().getParsers().getSupportedMediaTypes()) ); } private Encoder getEncoder() throws UnsupportedMediaType { if (encoder == null) { String ce = req.getHeaderParam("content-encoding").orElse(null); if (isNotEmpty(ce)) { ce = ce.trim(); encoder = encoders.getEncoder(ce); if (encoder == null) throw new UnsupportedMediaType( "Unsupported encoding in request header ''Content-Encoding'': ''{0}''\n\tSupported codings: {1}", req.getHeaderParam("content-encoding").orElse(null), Json5.of(encoders.getSupportedEncodings()) ); } if (encoder != null) contentLength = -1; } // Note that if this is the identity encoder, we want to return null // so that we don't needlessly wrap the input stream. if (encoder == IdentityEncoder.INSTANCE) return null; return encoder; } /** * Returns the content length of the content. * * @return The content length of the content in bytes. */ public int getContentLength() { return contentLength == 0 ? req.getHttpServletRequest().getContentLength() : contentLength; } /** * Caches the content in memory for reuse. * * @return This object. * @throws IOException If error occurs while reading stream. */ public RequestContent cache() throws IOException { if (content == null) content = readBytes(getInputStream()); return this; } //----------------------------------------------------------------------------------------------------------------- // Helper methods //----------------------------------------------------------------------------------------------------------------- private ClassMeta getClassMeta(Type type, Type...args) { return req.getBeanSession().getClassMeta(type, args); } private ClassMeta getClassMeta(Class type) { return req.getBeanSession().getClassMeta(type); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy