You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2018/01/06 03:39:36 UTC
[1/3] juneau git commit: RestContext refactoring.
Repository: juneau
Updated Branches:
refs/heads/master b766e1d07 -> 96fae4f98
http://git-wip-us.apache.org/repos/asf/juneau/blob/76b634af/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
index 3d727f6..d53cfa5 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
@@ -642,7 +642,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
* <b>Configuration property:</b> REST call handler.
*
* <p>
- * Same as {@link #callHandler(Class)} but allows you to pass in a call handler instance.
+ * Same as {@link #callHandler(Class)} except input is a pre-constructed instance.
*
* @param restHandler The new call handler for this resource.
* @return This object (for method chaining).
@@ -728,9 +728,9 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
/**
* <b>Configuration property:</b> Children.
- *
+ *
* <p>
- * Same as {@link #children(Class...)} but allows you to pass in already-constructed child instances.
+ * Same as {@link #children(Class...)} except input is pre-constructed instances.
*
* @param children
* The children to add to this resource.
@@ -766,38 +766,80 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
}
/**
- * <b>Configuration property:</b> Resource path.
+ * <b>Configuration property:</b> Classpath resource finder.
+ *
+ * <p>
+ * Used to retrieve localized files from the classpath.
+ *
+ * <h6 class='topic'>Notes:</h6>
+ * <ul class='spaced-list'>
+ * <li>Property: {@link RestContext#REST_classpathResourceFinder}
+ * <li>Annotations:
+ * <ul>
+ * <li>{@link RestResource#classpathResourceFinder()}
+ * </ul>
+ * <li>Methods:
+ * <ul>
+ * <li>{@link RestContextBuilder#classpathResourceFinder(Class)}
+ * <li>{@link RestContextBuilder#classpathResourceFinder(ClasspathResourceFinder)}
+ * </ul>
+ * <li>
+ * The default value is {@link ClasspathResourceFinderBasic} which provides basic support for finding localized
+ * resources on the classpath and JVM working directory.
+ * <br>The {@link ClasspathResourceFinderRecursive} is another option that also recursively searches for resources
+ * up the parent class hierarchy.
+ * <br>Each of these classes can be extended to provide customized handling of resource retrieval.
+ * </ul>
+ *
+ * @param classpathResourceFinder The resource finder class.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder classpathResourceFinder(Class<? extends ClasspathResourceFinder> classpathResourceFinder) {
+ return set(REST_classpathResourceFinder, classpathResourceFinder);
+ }
+
+ /**
+ * <b>Configuration property:</b> Classpath resource finder.
+ *
+ * <p>
+ * Same as {@link #classpathResourceFinder(ClasspathResourceFinder)} except input is a pre-constructed instance.
+ *
+ * @param classpathResourceFinder The resource finder instance.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder classpathResourceFinder(ClasspathResourceFinder classpathResourceFinder) {
+ return set(REST_classpathResourceFinder, classpathResourceFinder);
+ }
+
+ /**
+ * <b>Configuration property:</b> Client version header.
*
* <p>
- * Identifies the URL subpath relative to the parent resource.
+ * Specifies the name of the header used to denote the client version on HTTP requests.
*
* <p>
+ * The client version is used to support backwards compatibility for breaking REST interface changes.
+ * <br>Used in conjunction with {@link RestMethod#clientVersion()} annotation.
+ *
+ * <p>
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_path}
+ * <li>Property: {@link RestContext#REST_clientVersionHeader}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#path()}
- * </ul>
+ * <li>{@link RestResource#clientVersionHeader()}
+ * </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#path(String)}
+ * <li>{@link RestContextBuilder#clientVersionHeader(String)}
* </ul>
- * <li>This annotation is ignored on top-level servlets (i.e. servlets defined in <code>web.xml</code> files).
- * <br>Therefore, implementers can optionally specify a path value for documentation purposes.
- * <li>Typically, this setting is only applicable to resources defined as children through the
- * {@link RestResource#children()} annotation.
- * <br>However, it may be used in other ways (e.g. defining paths for top-level resources in microservices).
* </ul>
*
- * @param path The URL path of this resource.
+ * @param clientVersionHeader The name of the HTTP header that denotes the client version.
* @return This object (for method chaining).
*/
- public RestContextBuilder path(String path) {
- if (startsWith(path, '/'))
- path = path.substring(1);
- this.path = path;
- return this;
+ public RestContextBuilder clientVersionHeader(String clientVersionHeader) {
+ return set(REST_clientVersionHeader, clientVersionHeader);
}
/**
@@ -836,58 +878,54 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
}
/**
- * <b>Configuration property:</b> Render response stack traces in responses.
+ * <b>Configuration property:</b> Response converters.
*
* <p>
- * Render stack traces in HTTP response bodies when errors occur.
+ * Associates one or more {@link RestConverter converters} with a resource class.
+ * These converters get called immediately after execution of the REST method in the same order specified in the
+ * annotation.
+ *
+ * <p>
+ * Can be used for performing post-processing on the response object before serialization.
*
+ * <p>
+ * Default converter implementations are provided in the <a class='doclink'
+ * href='../converters/package-summary.html#TOC'>org.apache.juneau.rest.converters</a> package.
+ *
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_renderResponseStackTraces}
- * <li>Annotations:
+ * <li>Property: {@link RestContext#REST_converters}
+ * <li>Annotation:
* <ul>
- * <li>{@link RestResource#renderResponseStackTraces()}
+ * <li>{@link RestResource#converters()}
+ * <li>{@link RestMethod#converters()}
* </ul>
- * <li>Methods:
+ * <li>Method:
* <ul>
- * <li>{@link RestContextBuilder#renderResponseStackTraces(boolean)}
+ * <li>{@link RestContextBuilder#converters(Class...)}
+ * <li>{@link RestContextBuilder#converters(RestConverter...)}
* </ul>
- * <li>Useful for debugging, although allowing stack traces to be rendered may cause security concerns so use
- * caution when enabling.
+ * <li>{@link RestConverter} classes must have either a no-arg or {@link PropertyStore} argument constructors.
* </ul>
*
- * @param value The new value for this setting.
+ * @param converters The converter classes to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder renderResponseStackTraces(boolean value) {
- return set(REST_renderResponseStackTraces, value);
+ public RestContextBuilder converters(Class<?>...converters) {
+ return addTo(REST_converters, converters);
}
/**
- * <b>Configuration property:</b> Use stack trace hashes.
+ * <b>Configuration property:</b> Response converters.
*
* <p>
- * When enabled, the number of times an exception has occurred will be determined based on stack trace hashsums,
- * made available through the {@link RestException#getOccurrence()} method.
- *
- * <h5 class='section'>Notes:</h5>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_useStackTraceHashes}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#useStackTraceHashes()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#useStackTraceHashes(boolean)}
- * </ul>
- * </ul>
+ * Same as {@link #converters(Class...)} except input is pre-constructed instances.
*
- * @param value The new value for this setting.
+ * @param converters The converter classes to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder useStackTraceHashes(boolean value) {
- return set(REST_useStackTraceHashes, value);
+ public RestContextBuilder converters(RestConverter...converters) {
+ return addTo(REST_converters, converters);
}
/**
@@ -902,8 +940,8 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
* <li>Annotations:
* <ul>
* <li>{@link RestResource#defaultCharset()}
- <li>{@link RestMethod#defaultCharset()}
- </ul>
+ * <li>{@link RestMethod#defaultCharset()}
+ * </ul>
* <li>Methods:
* <ul>
* <li>{@link RestContextBuilder#defaultCharset(String)}
@@ -918,210 +956,171 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
}
/**
- * <b>Configuration property:</b> The maximum allowed input size (in bytes) on HTTP requests.
+ * <b>Configuration property:</b> Default request headers.
+ *
+ * <p>
+ * Adds class-level default HTTP request headers to this resource.
*
* <p>
- * Useful for alleviating DoS attacks by throwing an exception when too much input is received instead of resulting
- * in out-of-memory errors which could affect system stability.
- *
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_maxInput}
+ * <li>Property: {@link RestContext#REST_defaultRequestHeaders}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#maxInput()}
- * <li>{@link RestMethod#maxInput()}
+ * <li>{@link RestResource#defaultRequestHeaders()}
+ * <li>{@link RestMethod#defaultRequestHeaders()}
* </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#maxInput(String)}
+ * <li>{@link RestContextBuilder#defaultRequestHeader(String,Object)}
+ * <li>{@link RestContextBuilder#defaultRequestHeaders(String...)}
* </ul>
- * <li>String value that gets resolved to a <jk>long</jk>.
- * <li>Can be suffixed with any of the following representing kilobytes, megabytes, and gigabytes:
- * <js>'K'</js>, <js>'M'</js>, <js>'G'</js>.
- * <li>A value of <js>"-1"</js> can be used to represent no limit.
+ * <li>Strings are of the format <js>"Header-Name: header-value"</js>.
+ * <li>You can use either <js>':'</js> or <js>'='</js> as the key/value delimiter.
+ * <li>Key and value is trimmed of whitespace.
+ * <li>Only one header value can be specified per entry (i.e. it's not a delimited list of header entries).
+ * <li>Affects values returned by {@link RestRequest#getHeader(String)} when the header is not present on the request.
+ * <li>The most useful reason for this annotation is to provide a default <code>Accept</code> header when one is not
+ * specified so that a particular default {@link Serializer} is picked.
* </ul>
*
- * @param value The new value for this setting.
+ * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
* @return This object (for method chaining).
+ * @throws RestServletException If malformed header is found.
*/
- public RestContextBuilder maxInput(String value) {
- return set(REST_maxInput, value);
+ public RestContextBuilder defaultRequestHeaders(String...headers) throws RestServletException {
+ for (String header : headers) {
+ String[] h = RestUtils.parseHeader(header);
+ if (h == null)
+ throw new RestServletException("Invalid default request header specified: ''{0}''. Must be in the format: ''Header-Name: header-value''", header);
+ defaultRequestHeader(h[0], h[1]);
+ }
+ return this;
}
/**
- * <b>Configuration property:</b> Java method parameter resolvers.
- *
- * <p>
- * By default, the Juneau framework will automatically Java method parameters of various types (e.g.
- * <code>RestRequest</code>, <code>Accept</code>, <code>Reader</code>).
- * This annotation allows you to provide your own resolvers for your own class types that you want resolved.
+ * <b>Configuration property:</b> Default request headers.
*
* <p>
- * For example, if you want to pass in instances of <code>MySpecialObject</code> to your Java method, define
- * the following resolver:
- * <p class='bcode'>
- * <jk>public class</jk> MyRestParam <jk>extends</jk> RestParam {
+ * Same as {@link #defaultRequestHeaders(String...)} but adds a single header name/value pair.
*
- * <jc>// Must have no-arg constructor!</jc>
- * <jk>public</jk> MyRestParam() {
- * <jc>// First two parameters help with Swagger doc generation.</jc>
- * <jk>super</jk>(<jsf>QUERY</jsf>, <js>"myparam"</js>, MySpecialObject.<jk>class</jk>);
- * }
+ * @param name The HTTP header name.
+ * @param value The HTTP header value.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder defaultRequestHeader(String name, Object value) {
+ return addTo(REST_defaultRequestHeaders, name, value);
+ }
+
+ /**
+ * <b>Configuration property:</b> Default response headers.
*
- * <jc>// The method that creates our object.
- * // In this case, we're taking in a query parameter and converting it to our object.</jc>
- * <jk>public</jk> Object resolve(RestRequest req, RestResponse res) <jk>throws</jk> Exception {
- * <jk>return new</jk> MySpecialObject(req.getQuery().get(<js>"myparam"</js>));
- * }
- * }
- * </p>
+ * <p>
+ * Specifies default values for response headers.
*
+ * <p>
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_paramResolvers}
+ * <li>Property: {@link RestContext#REST_defaultResponseHeaders}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#paramResolvers()}
+ * <li>{@link RestResource#defaultResponseHeaders()}
* </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#paramResolvers(Class...)}
+ * <li>{@link RestContextBuilder#defaultResponseHeader(String,Object)}
+ * <li>{@link RestContextBuilder#defaultResponseHeaders(String...)}
* </ul>
- * <li>{@link RestParam} classes must have either a no-arg or {@link PropertyStore} argument constructors.
+ * <li>Strings are of the format <js>"Header-Name: header-value"</js>.
+ * <li>You can use either <js>':'</js> or <js>'='</js> as the key/value delimiter.
+ * <li>Key and value is trimmed of whitespace.
+ * <li>Only one header value can be specified per entry (i.e. it's not a delimited list of header entries).
+ * <li>This is equivalent to calling {@link RestResponse#setHeader(String, String)} programmatically in each of
+ * the Java methods.
+ * <li>The header value will not be set if the header value has already been specified (hence the 'default' in the name).
+ * <li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
+ * annotation.
* </ul>
*
- * @param paramResolvers The parameter resolvers to add to this config.
+ * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
* @return This object (for method chaining).
+ * @throws RestServletException If malformed header is found.
*/
- @SuppressWarnings("unchecked")
- public RestContextBuilder paramResolvers(Class<? extends RestParam>...paramResolvers) {
- return addTo(REST_paramResolvers, paramResolvers);
+ public RestContextBuilder defaultResponseHeaders(String...headers) throws RestServletException {
+ for (String header : headers) {
+ String[] h = RestUtils.parseHeader(header);
+ if (h == null)
+ throw new RestServletException("Invalid default response header specified: ''{0}''. Must be in the format: ''Header-Name: header-value''", header);
+ defaultResponseHeader(h[0], h[1]);
+ }
+ return this;
}
/**
- * <b>Configuration property:</b> Java method parameter resolvers.
- *
- * <p>
- * By default, the Juneau framework will automatically Java method parameters of various types (e.g.
- * <code>RestRequest</code>, <code>Accept</code>, <code>Reader</code>).
- * This annotation allows you to provide your own resolvers for your own class types that you want resolved.
+ * <b>Configuration property:</b> Default response headers.
*
* <p>
- * For example, if you want to pass in instances of <code>MySpecialObject</code> to your Java method, define
- * the following resolver:
- * <p class='bcode'>
- * <jk>public class</jk> MyRestParam <jk>extends</jk> RestParam {
+ * Same as {@link #defaultResponseHeaders(String...)} but adds a single header name/value pair.
*
- * <jc>// Must have no-arg constructor!</jc>
- * <jk>public</jk> MyRestParam() {
- * <jc>// First two parameters help with Swagger doc generation.</jc>
- * <jk>super</jk>(<jsf>QUERY</jsf>, <js>"myparam"</js>, MySpecialObject.<jk>class</jk>);
- * }
- *
- * <jc>// The method that creates our object.
- * // In this case, we're taking in a query parameter and converting it to our object.</jc>
- * <jk>public</jk> Object resolve(RestRequest req, RestResponse res) <jk>throws</jk> Exception {
- * <jk>return new</jk> MySpecialObject(req.getQuery().get(<js>"myparam"</js>));
- * }
- * }
- * </p>
- *
- * <h5 class='section'>Notes:</h5>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_paramResolvers}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#paramResolvers()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#paramResolvers(Class...)}
- * </ul>
- * <li>{@link RestParam} classes must have either a no-arg or {@link PropertyStore} argument constructors.
- * </ul>
- *
- * @param paramResolvers The parameter resolvers to add to this config.
+ * @param name The HTTP header name.
+ * @param value The HTTP header value.
* @return This object (for method chaining).
*/
- public RestContextBuilder paramResolvers(RestParam...paramResolvers) {
- return addTo(REST_paramResolvers, paramResolvers);
+ public RestContextBuilder defaultResponseHeader(String name, Object value) {
+ return addTo(REST_defaultResponseHeaders, name, value);
}
-
+
/**
- * <b>Configuration property:</b> Response converters.
- *
- * <p>
- * Associates one or more {@link RestConverter converters} with a resource class.
- * These converters get called immediately after execution of the REST method in the same order specified in the
- * annotation.
+ * <b>Configuration property:</b> Compression encoders.
*
* <p>
- * Can be used for performing post-processing on the response object before serialization.
+ * These can be used to enable various kinds of compression (e.g. <js>"gzip"</js>) on requests and responses.
*
- * <p>
- * Default converter implementations are provided in the <a class='doclink'
- * href='../converters/package-summary.html#TOC'>org.apache.juneau.rest.converters</a> package.
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * <jc>// Servlet with automated support for GZIP compression</jc>
+ * <ja>@RestResource</ja>(encoders={GzipEncoder.<jk>class</jk>})
+ * <jk>public</jk> MyRestServlet <jk>extends</jk> RestServlet {
+ * ...
+ * }
+ * </p>
*
- * <h5 class='section'>Notes:</h5>
+ * <h6 class='topic'>Notes:</h6>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_converters}
- * <li>Annotation:
+ * <li>Property: {@link RestContext#REST_encoders}
+ * <li>Annotations:
* <ul>
- * <li>{@link RestResource#converters()}
- * <li>{@link RestMethod#converters()}
+ * <li>{@link RestResource#encoders()}
+ * <li>{@link RestMethod#encoders()}
* </ul>
- * <li>Method:
+ * <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#converters(Class...)}
- * <li>{@link RestContextBuilder#converters(RestConverter...)}
+ * <li>{@link RestContextBuilder#encoders(Class...)}
+ * <li>{@link RestContextBuilder#encoders(Encoder...)}
* </ul>
- * <li>{@link RestConverter} classes must have either a no-arg or {@link PropertyStore} argument constructors.
- * </ul>
+ * <li>Instance classes must provide a public no-arg constructor, or a public constructor that takes in a
+ * {@link PropertyStore} object.
+ * <li>Instance class can be defined as an inner class of the REST resource class.
+ * </ul>
*
- * @param converters The converter classes to add to this config.
+ * @param encoders Encoder classes to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder converters(Class<?>...converters) {
- return addTo(REST_converters, converters);
+ public RestContextBuilder encoders(Class<?>...encoders) {
+ return addTo(REST_encoders, encoders);
}
/**
- * <b>Configuration property:</b> Response converters.
- *
- * <p>
- * Associates one or more {@link RestConverter converters} with a resource class.
- * These converters get called immediately after execution of the REST method in the same order specified in the
- * annotation.
- *
- * <p>
- * Can be used for performing post-processing on the response object before serialization.
+ * <b>Configuration property:</b> Compression encoders.
*
* <p>
- * Default converter implementations are provided in the <a class='doclink'
- * href='../converters/package-summary.html#TOC'>org.apache.juneau.rest.converters</a> package.
- *
- * <h5 class='section'>Notes:</h5>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_converters}
- * <li>Annotation:
- * <ul>
- * <li>{@link RestResource#converters()}
- * <li>{@link RestMethod#converters()}
- * </ul>
- * <li>Method:
- * <ul>
- * <li>{@link RestContextBuilder#converters(Class...)}
- * <li>{@link RestContextBuilder#converters(RestConverter...)}
- * </ul>
- * <li>{@link RestConverter} classes must have either a no-arg or {@link PropertyStore} argument constructors.
- * </ul>
+ * Same as {@link #encoders(Class...)} except input a pre-constructed instances.
*
- * @param converters The converter classes to add to this config.
+ * @param encoders Encoder instances to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder converters(RestConverter...converters) {
- return addTo(REST_converters, converters);
+ public RestContextBuilder encoders(Encoder...encoders) {
+ return addTo(REST_encoders, encoders);
}
/**
@@ -1164,30 +1163,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
* <b>Configuration property:</b> Class-level guards.
*
* <p>
- * Associates one or more {@link RestGuard RestGuards} with all REST methods defined in this class.
- * These guards get called immediately before execution of any REST method in this class.
- *
- * <p>
- * Typically, guards will be used for permissions checking on the user making the request, but it can also be used
- * for other purposes like pre-call validation of a request.
- *
- * <h5 class='section'>Notes:</h5>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_guards}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#guards()}
- * <li>{@link RestMethod#guards()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#guards(Class...)}
- * <li>{@link RestContextBuilder#guards(RestGuard...)}
- * </ul>
- * <li>{@link RestGuard} classes must have either a no-arg or {@link PropertyStore} argument constructors.
- * <li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
- * annotation.
- * </ul>
+ * Same as {@link #guards(Class...)} except input is pre-constructed instances.
*
* @param guards The guard classes to add to this config.
* @return This object (for method chaining).
@@ -1197,442 +1173,556 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
}
/**
- * <b>Configuration property:</b> Response handlers.
+ * <b>Configuration property:</b> REST info provider.
*
* <p>
- * Specifies a list of {@link ResponseHandler} classes that know how to convert POJOs returned by REST methods or
- * set via {@link RestResponse#setOutput(Object)} into appropriate HTTP responses.
+ * Class used to retrieve title/description/swagger information about a resource.
*
* <p>
- * By default, the following response handlers are provided out-of-the-box:
- * <ul>
- * <li>{@link StreamableHandler}
- * <li>{@link WritableHandler}
- * <li>{@link ReaderHandler}
- * <li>{@link InputStreamHandler}
- * <li>{@link RedirectHandler}
- * <li>{@link DefaultHandler}
- * </ul>
-
+ * Subclasses can be used to customize the documentation on a resource.
+ *
* <p>
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_responseHandlers}
+ * <li>Property: {@link RestContext#REST_infoProvider}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#responseHandlers()}
+ * <li>{@link RestResource#infoProvider()}
* </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#responseHandlers(Class...)}
- * <li>{@link RestContextBuilder#responseHandlers(ResponseHandler...)}
+ * <li>{@link RestContextBuilder#infoProvider(Class)}
+ * <li>{@link RestContextBuilder#infoProvider(RestInfoProvider)}
* </ul>
- * <li>{@link ResponseHandler} classes must have either a no-arg or {@link PropertyStore} argument constructors.
* </ul>
*
- * @param responseHandlers The response handlers to add to this config.
+ * @param infoProvider The new info provider for this resource.
* @return This object (for method chaining).
*/
- public RestContextBuilder responseHandlers(Class<?>...responseHandlers) {
- return addTo(REST_responseHandlers, responseHandlers);
+ public RestContextBuilder infoProvider(Class<? extends RestInfoProvider> infoProvider) {
+ return set(REST_infoProvider, infoProvider);
}
/**
- * <b>Configuration property:</b> Response handlers.
+ * <b>Configuration property:</b> REST info provider.
*
* <p>
- * Specifies a list of {@link ResponseHandler} classes that know how to convert POJOs returned by REST methods or
- * set via {@link RestResponse#setOutput(Object)} into appropriate HTTP responses.
+ * Same as {@link #infoProvider(Class)} except input is a pre-constructed instance.
*
- * <p>
- * By default, the following response handlers are provided out-of-the-box:
- * <ul>
- * <li>{@link StreamableHandler}
- * <li>{@link WritableHandler}
- * <li>{@link ReaderHandler}
- * <li>{@link InputStreamHandler}
- * <li>{@link RedirectHandler}
- * <li>{@link DefaultHandler}
- * </ul>
+ * @param infoProvider The new info provider for this resource.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder infoProvider(RestInfoProvider infoProvider) {
+ return set(REST_infoProvider, infoProvider);
+ }
+ /**
+ * <b>Configuration property:</b> REST logger.
+ *
+ * <p>
+ * Specifies the logger to use for logging.
+ *
+ * <p>
+ * The default logger performs basic error logging to the Java logger.
+ * <br>Subclasses can be used to customize logging behavior on the resource.
+ *
* <p>
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_responseHandlers}
+ * <li>Property: {@link RestContext#REST_logger}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#responseHandlers()}
+ * <li>{@link RestResource#logger()}
* </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#responseHandlers(Class...)}
- * <li>{@link RestContextBuilder#responseHandlers(ResponseHandler...)}
+ * <li>{@link RestContextBuilder#logger(Class)}
+ * <li>{@link RestContextBuilder#logger(RestLogger)}
* </ul>
- * <li>{@link ResponseHandler} classes must have either a no-arg or {@link PropertyStore} argument constructors.
* </ul>
*
- * @param responseHandlers The response handlers to add to this config.
+ * @param logger The new logger for this resource. Can be <jk>null</jk> to disable logging.
* @return This object (for method chaining).
*/
- public RestContextBuilder responseHandlers(ResponseHandler...responseHandlers) {
- return addTo(REST_responseHandlers, responseHandlers);
+ public RestContextBuilder logger(Class<? extends RestLogger> logger) {
+ return set(REST_logger, logger);
}
/**
- * <b>Configuration property:</b> Default request headers.
- *
+ * <b>Configuration property:</b> REST logger.
+ *
* <p>
- * Adds class-level default HTTP request headers to this resource.
+ * Same as {@link #logger(Class)} except input is a pre-constructed instance.
+ *
+ * @param logger The new logger for this resource. Can be <jk>null</jk> to disable logging.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder logger(RestLogger logger) {
+ return set(REST_logger, logger);
+ }
+
+ /**
+ * <b>Configuration property:</b> The maximum allowed input size (in bytes) on HTTP requests.
*
* <p>
+ * Useful for alleviating DoS attacks by throwing an exception when too much input is received instead of resulting
+ * in out-of-memory errors which could affect system stability.
+ *
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_defaultRequestHeaders}
+ * <li>Property: {@link RestContext#REST_maxInput}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#defaultRequestHeaders()}
- * <li>{@link RestMethod#defaultRequestHeaders()}
+ * <li>{@link RestResource#maxInput()}
+ * <li>{@link RestMethod#maxInput()}
* </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#defaultRequestHeader(String,Object)}
- * <li>{@link RestContextBuilder#defaultRequestHeaders(String...)}
+ * <li>{@link RestContextBuilder#maxInput(String)}
* </ul>
- * <li>Affects values returned by {@link RestRequest#getHeader(String)} when the header is not present on the request.
- * <li>The most useful reason for this annotation is to provide a default <code>Accept</code> header when one is not
- * specified so that a particular default {@link Serializer} is picked.
+ * <li>String value that gets resolved to a <jk>long</jk>.
+ * <li>Can be suffixed with any of the following representing kilobytes, megabytes, and gigabytes:
+ * <js>'K'</js>, <js>'M'</js>, <js>'G'</js>.
+ * <li>A value of <js>"-1"</js> can be used to represent no limit.
* </ul>
*
- * @param name The HTTP header name.
- * @param value The HTTP header value.
+ * @param value The new value for this setting.
* @return This object (for method chaining).
*/
- public RestContextBuilder defaultRequestHeader(String name, Object value) {
- return addTo(REST_defaultRequestHeaders, name, value);
+ public RestContextBuilder maxInput(String value) {
+ return set(REST_maxInput, value);
}
/**
- * <b>Configuration property:</b> Default request headers.
+ * <b>Configuration property:</b> Messages.
*
* <p>
- * Adds class-level default HTTP request headers to this resource.
+ * Identifies the location of the resource bundle for this class.
*
* <p>
- * <h5 class='section'>Notes:</h5>
+ * This annotation is used to provide localized messages for the following methods:
+ * <ul>
+ * <li>{@link RestRequest#getMessage(String, Object...)}
+ * <li>{@link RestContext#getMessages()}
+ * </ul>
+ *
+ * <p>
+ * Refer to the {@link MessageBundle} class for a description of the message key formats used in the properties file.
+ *
+ * <p>
+ * The value can be a relative path like <js>"nls/Messages"</js>, indicating to look for the resource bundle
+ * <js>"com.foo.sample.nls.Messages"</js> if the resource class is in <js>"com.foo.sample"</js>, or it can be an
+ * absolute path, like <js>"com.foo.sample.nls.Messages"</js>
+ *
+ * <h6 class='topic'>Notes:</h6>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_defaultRequestHeaders}
- * <li>Annotations:
+ * <li>Property: {@link RestContext#REST_messages}
+ * <li>Annotations:
* <ul>
- * <li>{@link RestResource#defaultRequestHeaders()}
- * <li>{@link RestMethod#defaultRequestHeaders()}
+ * <li>{@link RestResource#messages()}
* </ul>
- * <li>Methods:
+ * <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#defaultRequestHeader(String,Object)}
- * <li>{@link RestContextBuilder#defaultRequestHeaders(String...)}
+ * <li>{@link RestContextBuilder#messages(String)},
+ * <li>{@link RestContextBuilder#messages(Class,String)}
+ * <li>{@link RestContextBuilder#messages(MessageBundleLocation)}
* </ul>
- * <li>Strings are of the format <js>"Header-Name: header-value"</js>.
- * <li>You can use either <js>':'</js> or <js>'='</js> as the key/value delimiter.
- * <li>Key and value is trimmed of whitespace.
- * <li>Only one header value can be specified per entry (i.e. it's not a delimited list of header entries).
- * <li>Affects values returned by {@link RestRequest#getHeader(String)} when the header is not present on the request.
- * <li>The most useful reason for this annotation is to provide a default <code>Accept</code> header when one is not
- * specified so that a particular default {@link Serializer} is picked.
- * </ul>
+ * <li>Mappings are cumulative from parent to child.
+ * </ul>
*
- * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
+ * @param messageBundleLocation The message bundle location to add to the search path.
* @return This object (for method chaining).
- * @throws RestServletException If malformed header is found.
*/
- public RestContextBuilder defaultRequestHeaders(String...headers) throws RestServletException {
- for (String header : headers) {
- String[] h = RestUtils.parseHeader(header);
- if (h == null)
- throw new RestServletException("Invalid default request header specified: ''{0}''. Must be in the format: ''Header-Name: header-value''", header);
- defaultRequestHeader(h[0], h[1]);
- }
- return this;
+ public RestContextBuilder messages(MessageBundleLocation messageBundleLocation) {
+ return addTo(REST_messages, messageBundleLocation);
}
/**
- * <b>Configuration property:</b> Default response headers.
+ * <b>Configuration property:</b> Messages.
*
* <p>
- * Specifies default values for response headers.
+ * Same as {@link #messages(MessageBundleLocation)} except allows you to pass in the base class and bundle
+ * path separately.
+ *
+ * @param baseClass
+ * The base class that the bundle path is relative to.
+ * <br>If <jk>null</jk>, assumed to be the resource class itself.
+ * @param bundlePath The bundle path relative to the base class.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder messages(Class<?> baseClass, String bundlePath) {
+ return addTo(REST_messages, new MessageBundleLocation(baseClass, bundlePath));
+ }
+
+ /**
+ * <b>Configuration property:</b> Messages.
*
* <p>
- * <h5 class='section'>Notes:</h5>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_defaultResponseHeaders}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#defaultResponseHeaders()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#defaultResponseHeader(String,Object)}
- * <li>{@link RestContextBuilder#defaultResponseHeaders(String...)}
- * </ul>
- * <li>This is equivalent to calling {@link RestResponse#setHeader(String, String)} programmatically in each of
- * the Java methods.
- * <li>The header value will not be set if the header value has already been specified (hence the 'default' in the name).
- * <li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
- * annotation.
- * </ul>
- *
- * @param name The HTTP header name.
- * @param value The HTTP header value.
+ * Same as {@link #messages(Class,String)} except assumes the base class is the resource class itself.
+ *
+ * @param bundlePath The bundle path relative to the base class.
* @return This object (for method chaining).
*/
- public RestContextBuilder defaultResponseHeader(String name, Object value) {
- return addTo(REST_defaultResponseHeaders, name, value);
+ public RestContextBuilder messages(String bundlePath) {
+ return addTo(REST_messages, new MessageBundleLocation(null, bundlePath));
}
/**
- * <b>Configuration property:</b> Default response headers.
+ * <b>Configuration property:</b> MIME types.
*
* <p>
- * Specifies default values for response headers.
- *
+ * Defines MIME-type file type mappings.
+ *
* <p>
- * <h5 class='section'>Notes:</h5>
+ * Used for specifying the content type on file resources retrieved through the following methods:
+ * <ul>
+ * <li>{@link RestContext#resolveStaticFile(String)}
+ * <li>{@link RestRequest#getClasspathReaderResource(String,boolean,MediaType)}
+ * <li>{@link RestRequest#getClasspathReaderResource(String,boolean)}
+ * <li>{@link RestRequest#getClasspathReaderResource(String)}
+ * </ul>
+ *
+ * <p>
+ * This list appends to the existing list provided by {@link ExtendedMimetypesFileTypeMap}.
+ *
+ * <h6 class='topic'>Notes:</h6>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_defaultResponseHeaders}
- * <li>Annotations:
+ * <li>Property: {@link RestContext#REST_mimeTypes}
+ * <li>Annotations:
* <ul>
- * <li>{@link RestResource#defaultResponseHeaders()}
+ * <li>{@link RestResource#mimeTypes()}
* </ul>
- * <li>Methods:
+ * <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#defaultResponseHeader(String,Object)}
- * <li>{@link RestContextBuilder#defaultResponseHeaders(String...)}
+ * <li>{@link RestContextBuilder#mimeTypes(String...)}
* </ul>
- * <li>Strings are of the format <js>"Header-Name: header-value"</js>.
- * <li>You can use either <js>':'</js> or <js>'='</js> as the key/value delimiter.
- * <li>Key and value is trimmed of whitespace.
- * <li>Only one header value can be specified per entry (i.e. it's not a delimited list of header entries).
- * <li>This is equivalent to calling {@link RestResponse#setHeader(String, String)} programmatically in each of
- * the Java methods.
- * <li>The header value will not be set if the header value has already been specified (hence the 'default' in the name).
- * <li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
- * annotation.
- * </ul>
+ * <li>Values are .mime.types formatted entry string.
+ * <br>Example: <js>"image/svg+xml svg"</js>
+ * </ul>
*
- * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
+ * @param mimeTypes The MIME-types to add to this config.
* @return This object (for method chaining).
- * @throws RestServletException If malformed header is found.
*/
- public RestContextBuilder defaultResponseHeaders(String...headers) throws RestServletException {
- for (String header : headers) {
- String[] h = RestUtils.parseHeader(header);
- if (h == null)
- throw new RestServletException("Invalid default response header specified: ''{0}''. Must be in the format: ''Header-Name: header-value''", header);
- defaultResponseHeader(h[0], h[1]);
- }
- return this;
+ public RestContextBuilder mimeTypes(String...mimeTypes) {
+ return addTo(REST_mimeTypes, mimeTypes);
}
/**
- * <b>Configuration property:</b> Supported accept media types.
+ * <b>Configuration property:</b> Java method parameter resolvers.
*
* <p>
- * Overrides the media types inferred from the serializers that identify what media types can be produced by the resource.
- *
- * <p>
- * This affects the values returned by {@link RestRequest#getSupportedAcceptTypes()} and the supported accept
- * types shown in {@link RestInfoProvider#getSwagger(RestRequest)}.
+ * By default, the Juneau framework will automatically Java method parameters of various types (e.g.
+ * <code>RestRequest</code>, <code>Accept</code>, <code>Reader</code>).
+ * This annotation allows you to provide your own resolvers for your own class types that you want resolved.
*
* <p>
+ * For example, if you want to pass in instances of <code>MySpecialObject</code> to your Java method, define
+ * the following resolver:
+ * <p class='bcode'>
+ * <jk>public class</jk> MyRestParam <jk>extends</jk> RestParam {
+ *
+ * <jc>// Must have no-arg constructor!</jc>
+ * <jk>public</jk> MyRestParam() {
+ * <jc>// First two parameters help with Swagger doc generation.</jc>
+ * <jk>super</jk>(<jsf>QUERY</jsf>, <js>"myparam"</js>, MySpecialObject.<jk>class</jk>);
+ * }
+ *
+ * <jc>// The method that creates our object.
+ * // In this case, we're taking in a query parameter and converting it to our object.</jc>
+ * <jk>public</jk> Object resolve(RestRequest req, RestResponse res) <jk>throws</jk> Exception {
+ * <jk>return new</jk> MySpecialObject(req.getQuery().get(<js>"myparam"</js>));
+ * }
+ * }
+ * </p>
+ *
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_supportedAcceptTypes}
+ * <li>Property: {@link RestContext#REST_paramResolvers}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#supportedAcceptTypes()}
- * <li>{@link RestMethod#supportedAcceptTypes()}
- * </ul>
- * <li>Methods:
+ * <li>{@link RestResource#paramResolvers()}
+ * </ul>
+ * <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#supportedAcceptTypes(boolean,String...)}
- * <li>{@link RestContextBuilder#supportedAcceptTypes(boolean,MediaType...)}
+ * <li>{@link RestContextBuilder#paramResolvers(Class...)}
* </ul>
+ * <li>{@link RestParam} classes must have either a no-arg or {@link PropertyStore} argument constructors.
* </ul>
*
- * @param append
- * If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
- * @param mediaTypes The new list of media types supported by this resource.
+ * @param paramResolvers The parameter resolvers to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder supportedAcceptTypes(boolean append, String...mediaTypes) {
- return set(append, REST_supportedAcceptTypes, mediaTypes);
+ @SuppressWarnings("unchecked")
+ public RestContextBuilder paramResolvers(Class<? extends RestParam>...paramResolvers) {
+ return addTo(REST_paramResolvers, paramResolvers);
}
/**
- * <b>Configuration property:</b> Supported accept media types.
+ * <b>Configuration property:</b> Java method parameter resolvers.
*
* <p>
- * Overrides the media types inferred from the serializers that identify what media types can be produced by the resource.
+ * Same as {@link #paramResolvers(Class...)} except input is pre-constructed instances.
+ *
+ * @param paramResolvers The parameter resolvers to add to this config.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder paramResolvers(RestParam...paramResolvers) {
+ return addTo(REST_paramResolvers, paramResolvers);
+ }
+
+ /**
+ * <b>Configuration property:</b> Parser listener.
*
* <p>
- * This affects the values returned by {@link RestRequest#getSupportedAcceptTypes()} and the supported accept
- * types shown in {@link RestInfoProvider#getSwagger(RestRequest)}.
+ * Specifies the parser listener class to use for listening to non-fatal parsing errors.
*
* <p>
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_supportedAcceptTypes}
+ * <li>Property: {@link Parser#PARSER_listener}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#supportedAcceptTypes()}
- * <li>{@link RestMethod#supportedAcceptTypes()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#supportedAcceptTypes(boolean,String...)}
- * <li>{@link RestContextBuilder#supportedAcceptTypes(boolean,MediaType...)}
+ * <li>{@link RestResource#parserListener()}
* </ul>
+ * <li>Methods:
+ * <ul>
+ * <li>{@link RestContextBuilder#parserListener(Class)}
+ * </ul>
* </ul>
*
- * @param append
- * If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
- * @param mediaTypes The new list of media types supported by this resource.
+ * @param listener The listener to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder supportedAcceptTypes(boolean append, MediaType...mediaTypes) {
- return set(append, REST_supportedAcceptTypes, mediaTypes);
+ public RestContextBuilder parserListener(Class<? extends ParserListener> listener) {
+ return set(PARSER_listener, listener);
}
/**
- * <b>Configuration property:</b> Supported content media types.
- *
- * <p>
- * Overrides the media types inferred from the parsers that identify what media types can be consumed by the resource.
+ * <b>Configuration property:</b> Parsers.
*
* <p>
- * This affects the values returned by {@link RestRequest#getSupportedContentTypes()} and the supported content
- * types shown in {@link RestInfoProvider#getSwagger(RestRequest)}.
+ * Adds class-level parsers to this resource.
*
- * <p>
- * <h5 class='section'>Notes:</h5>
+ * <h6 class='topic'>Notes:</h6>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_supportedContentTypes}
- * <li>Annotations:
+ * <li>Property: {@link RestContext#REST_parsers}
+ * <li>Annotations:
* <ul>
- * <li>{@link RestResource#supportedContentTypes()}
- * <li>{@link RestMethod#supportedContentTypes()}
- * </ul>
- * <li>Methods:
+ * <li>{@link RestResource#parsers()}
+ * <li>{@link RestMethod#parsers()}
+ * </ul>
+ * <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#supportedContentTypes(boolean,String...)}
- * <li>{@link RestContextBuilder#supportedContentTypes(boolean,MediaType...)}
+ * <li>{@link RestContextBuilder#parsers(Class...)}
+ * <li>{@link RestContextBuilder#parsers(boolean,Class...)}
+ * <li>{@link RestContextBuilder#parsers(Parser...)}
+ * <li>{@link RestContextBuilder#parsers(boolean,Parser...)}
* </ul>
- * </ul>
+ * <li>When defined as a class, properties/transforms defined on the resource/method are inherited.
+ * <li>When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
+ * <li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
+ * annotation.
+ * </ul>
+ *
+ * @param parsers The parser classes to add to this config.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder parsers(Class<?>...parsers) {
+ return addTo(REST_parsers, parsers);
+ }
+
+ /**
+ * <b>Configuration property:</b> Parsers.
*
+ * <p>
+ * Same as {@link #parsers(Class...)} except allows you to overwrite the previous value.
+ *
* @param append
* If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
- * @param mediaTypes The new list of media types supported by this resource.
+ * @param parsers The parser classes to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder supportedContentTypes(boolean append, String...mediaTypes) {
- return set(append, REST_supportedContentTypes, mediaTypes);
+ public RestContextBuilder parsers(boolean append, Class<?>...parsers) {
+ return set(append, REST_parsers, parsers);
}
/**
- * <b>Configuration property:</b> Supported content media types.
+ * <b>Configuration property:</b> Parsers.
*
* <p>
- * Overrides the media types inferred from the parsers that identify what media types can be consumed by the resource.
+ * Same as {@link #parsers(Class...)} except input is pre-constructed instances.
+ *
+ * <p>
+ * Parser instances are considered set-in-stone and do NOT inherit properties and transforms defined on the
+ * resource class or method.
+ *
+ * @param parsers The parsers to add to this config.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder parsers(Parser...parsers) {
+ return addTo(REST_parsers, parsers);
+ }
+
+ /**
+ * <b>Configuration property:</b> Parsers.
*
* <p>
- * This affects the values returned by {@link RestRequest#getSupportedContentTypes()} and the supported content
- * types shown in {@link RestInfoProvider#getSwagger(RestRequest)}.
+ * Same as {@link #parsers(Parser...)} except allows you to overwrite the previous value.
*
+ * @param append
+ * If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
+ * @param parsers The parsers to add to this config.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder parsers(boolean append, Parser...parsers) {
+ return set(append, REST_parsers, parsers);
+ }
+
+ /**
+ * <b>Configuration property:</b> HTTP part parser.
+ *
* <p>
- * <h5 class='section'>Notes:</h5>
+ * Specifies the {@link HttpPartParser} to use for parsing headers, query/form parameters, and URI parts.
+ *
+ * <h6 class='topic'>Notes:</h6>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_supportedContentTypes}
- * <li>Annotations:
+ * <li>Property: {@link RestContext#REST_partParser}
+ * <li>Annotations:
* <ul>
- * <li>{@link RestResource#supportedContentTypes()}
- * <li>{@link RestMethod#supportedContentTypes()}
- * </ul>
- * <li>Methods:
+ * <li>{@link RestResource#partParser()}
+ * </ul>
+ * <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#supportedContentTypes(boolean,String...)}
- * <li>{@link RestContextBuilder#supportedContentTypes(boolean,MediaType...)}
+ * <li>{@link RestContextBuilder#partParser(Class)}
+ * <li>{@link RestContextBuilder#partParser(HttpPartParser)}
* </ul>
- * </ul>
+ * <li>When defined as a class, properties/transforms defined on the resource/method are inherited.
+ * <li>When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
+ * </ul>
*
- * @param append
- * If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
- * @param mediaTypes The new list of media types supported by this resource.
+ * @param partParser The parser class.
* @return This object (for method chaining).
*/
- public RestContextBuilder supportedContentTypes(boolean append, MediaType...mediaTypes) {
- return set(append, REST_supportedContentTypes, mediaTypes);
+ public RestContextBuilder partParser(Class<? extends HttpPartParser> partParser) {
+ return set(REST_partParser, partParser);
}
/**
- * <b>Configuration property:</b> Client version header.
+ * <b>Configuration property:</b> HTTP part parser.
*
* <p>
- * Specifies the name of the header used to denote the client version on HTTP requests.
+ * Same as {@link #partParser(Class)} except input is a pre-constructed instance.
+ *
+ * @param partParser The parser instance.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder partParser(HttpPartParser partParser) {
+ return set(REST_partParser, partParser);
+ }
+
+ /**
+ * <b>Configuration property:</b> HTTP part serializer.
*
* <p>
- * The client version is used to support backwards compatibility for breaking REST interface changes.
- * <br>Used in conjunction with {@link RestMethod#clientVersion()} annotation.
- *
+ * Specifies the {@link HttpPartSerializer} to use for serializing headers, query/form parameters, and URI parts.
+ *
+ * <h6 class='topic'>Notes:</h6>
+ * <ul class='spaced-list'>
+ * <li>Property: {@link RestContext#REST_partSerializer}
+ * <li>Annotations:
+ * <ul>
+ * <li>{@link RestResource#partSerializer()}
+ * </ul>
+ * <li>Methods:
+ * <ul>
+ * <li>{@link RestContextBuilder#partSerializer(Class)}
+ * <li>{@link RestContextBuilder#partSerializer(HttpPartSerializer)}
+ * </ul>
+ * <li>When defined as a class, properties/transforms defined on the resource/method are inherited.
+ * <li>When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
+ * </ul>
+ *
+ * @param partSerializer The serializer class.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder partSerializer(Class<? extends HttpPartSerializer> partSerializer) {
+ return set(REST_partSerializer, partSerializer);
+ }
+
+ /**
+ * <b>Configuration property:</b> HTTP part serializer.
+ *
+ * <p>
+ * Same as {@link #partSerializer(Class)} except input is a pre-constructed instance.
+ *
+ * @param partSerializer The serializer instance.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder partSerializer(HttpPartSerializer partSerializer) {
+ return set(REST_partSerializer, partSerializer);
+ }
+
+ /**
+ * <b>Configuration property:</b> Resource path.
+ *
+ * <p>
+ * Identifies the URL subpath relative to the parent resource.
+ *
* <p>
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_clientVersionHeader}
+ * <li>Property: {@link RestContext#REST_path}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#clientVersionHeader()}
- * </ul>
+ * <li>{@link RestResource#path()}
+ * </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#clientVersionHeader(String)}
+ * <li>{@link RestContextBuilder#path(String)}
* </ul>
+ * <li>This annotation is ignored on top-level servlets (i.e. servlets defined in <code>web.xml</code> files).
+ * <br>Therefore, implementers can optionally specify a path value for documentation purposes.
+ * <li>Typically, this setting is only applicable to resources defined as children through the
+ * {@link RestResource#children()} annotation.
+ * <br>However, it may be used in other ways (e.g. defining paths for top-level resources in microservices).
* </ul>
*
- * @param clientVersionHeader The name of the HTTP header that denotes the client version.
+ * @param path The URL path of this resource.
* @return This object (for method chaining).
*/
- public RestContextBuilder clientVersionHeader(String clientVersionHeader) {
- return set(REST_clientVersionHeader, clientVersionHeader);
+ public RestContextBuilder path(String path) {
+ if (startsWith(path, '/'))
+ path = path.substring(1);
+ this.path = path;
+ return this;
}
/**
- * REST resource resolver.
- *
- * <p>
- * The resolver used for resolving child resources.
- *
- * <p>
- * Can be used to provide customized resolution of REST resource class instances (e.g. resources retrieve from Spring).
+ * <b>Configuration property:</b> Render response stack traces in responses.
*
* <p>
+ * Render stack traces in HTTP response bodies when errors occur.
+ *
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_resourceResolver}
+ * <li>Property: {@link RestContext#REST_renderResponseStackTraces}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#resourceResolver()}
+ * <li>{@link RestResource#renderResponseStackTraces()}
* </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#resourceResolver(Class)}
- * <li>{@link RestContextBuilder#resourceResolver(RestResourceResolver)}
+ * <li>{@link RestContextBuilder#renderResponseStackTraces(boolean)}
* </ul>
- * <li>Unless overridden, resource resolvers are inherited from parent resources.
+ * <li>Useful for debugging, although allowing stack traces to be rendered may cause security concerns so use
+ * caution when enabling.
* </ul>
*
- * @param resourceResolver The new resource resolver.
+ * @param value The new value for this setting.
* @return This object (for method chaining).
*/
- public RestContextBuilder resourceResolver(Class<? extends RestResourceResolver> resourceResolver) {
- return set(REST_resourceResolver, resourceResolver);
+ public RestContextBuilder renderResponseStackTraces(boolean value) {
+ return set(REST_renderResponseStackTraces, value);
}
/**
@@ -1655,673 +1745,110 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
* <li>Methods:
* <ul>
* <li>{@link RestContextBuilder#resourceResolver(Class)}
- <li>{@link RestContextBuilder#resourceResolver(RestResourceResolver)}
- </ul>
+ * <li>{@link RestContextBuilder#resourceResolver(RestResourceResolver)}
+ * </ul>
* <li>Unless overridden, resource resolvers are inherited from parent resources.
* </ul>
*
* @param resourceResolver The new resource resolver.
* @return This object (for method chaining).
*/
- public RestContextBuilder resourceResolver(RestResourceResolver resourceResolver) {
+ public RestContextBuilder resourceResolver(Class<? extends RestResourceResolver> resourceResolver) {
return set(REST_resourceResolver, resourceResolver);
}
/**
- * <b>Configuration property:</b> REST logger.
+ * REST resource resolver.
*
* <p>
- * Specifies the logger to use for logging.
+ * Same as {@link #resourceResolver(Class)} except input is a pre-constructed instance.
+ *
+ * @param resourceResolver The new resource resolver.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder resourceResolver(RestResourceResolver resourceResolver) {
+ return set(REST_resourceResolver, resourceResolver);
+ }
+
+ /**
+ * <b>Configuration property:</b> Response handlers.
*
* <p>
- * The default logger performs basic error logging to the Java logger.
- * <br>Subclasses can be used to customize logging behavior on the resource.
- *
+ * Specifies a list of {@link ResponseHandler} classes that know how to convert POJOs returned by REST methods or
+ * set via {@link RestResponse#setOutput(Object)} into appropriate HTTP responses.
+ *
+ * <p>
+ * By default, the following response handlers are provided out-of-the-box:
+ * <ul>
+ * <li>{@link StreamableHandler}
+ * <li>{@link WritableHandler}
+ * <li>{@link ReaderHandler}
+ * <li>{@link InputStreamHandler}
+ * <li>{@link RedirectHandler}
+ * <li>{@link DefaultHandler}
+ * </ul>
+
* <p>
* <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_logger}
+ * <li>Property: {@link RestContext#REST_responseHandlers}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#logger()}
+ * <li>{@link RestResource#responseHandlers()}
* </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#logger(Class)}
- * <li>{@link RestContextBuilder#logger(RestLogger)}
- * </ul>
- * </ul>
- *
- * @param logger The new logger for this resource. Can be <jk>null</jk> to disable logging.
- * @return This object (for method chaining).
- */
- public RestContextBuilder logger(Class<? extends RestLogger> logger) {
- return set(REST_logger, logger);
- }
-
- /**
- * <b>Configuration property:</b> REST logger.
- *
- * <p>
- * Specifies the logger to use for logging.
- *
- * <p>
- * The default logger performs basic error logging to the Java logger.
- * <br>Subclasses can be used to customize logging behavior on the resource.
- *
- * <p>
- * <h5 class='section'>Notes:</h5>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_logger}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#logger()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#logger(Class)}
- * <li>{@link RestContextBuilder#logger(RestLogger)}
- * </ul>
- * </ul>
- *
- * @param logger The new logger for this resource. Can be <jk>null</jk> to disable logging.
- * @return This object (for method chaining).
- */
- public RestContextBuilder logger(RestLogger logger) {
- return set(REST_logger, logger);
- }
-
- /**
- * <b>Configuration property:</b> REST info provider.
- *
- * <p>
- * Class used to retrieve title/description/swagger information about a resource.
- *
- * <p>
- * Subclasses can be used to customize the documentation on a resource.
- *
- * <p>
- * <h5 class='section'>Notes:</h5>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_infoProvider}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#infoProvider()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#infoProvider(Class)}
- * <li>{@link RestContextBuilder#infoProvider(RestInfoProvider)}
- * </ul>
- * </ul>
- *
- * @param infoProvider The new info provider for this resource.
- * @return This object (for method chaining).
- */
- public RestContextBuilder infoProvider(Class<? extends RestInfoProvider> infoProvider) {
- return set(REST_infoProvider, infoProvider);
- }
-
- /**
- * <b>Configuration property:</b> REST info provider.
- *
- * <p>
- * Class used to retrieve title/description/swagger information about a resource.
- *
- * <p>
- * Subclasses can be used to customize the documentation on a resource.
- *
- * <p>
- * <h5 class='section'>Notes:</h5>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_infoProvider}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#infoProvider()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#infoProvider(Class)}
- * <li>{@link RestContextBuilder#infoProvider(RestInfoProvider)}
+ * <li>{@link RestContextBuilder#responseHandlers(Class...)}
+ * <li>{@link RestContextBuilder#responseHandlers(ResponseHandler...)}
* </ul>
+ * <li>{@link ResponseHandler} classes must have either a no-arg or {@link PropertyStore} argument constructors.
* </ul>
*
- * @param infoProvider The new info provider for this resource.
+ * @param responseHandlers The response handlers to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder infoProvider(RestInfoProvider infoProvider) {
- return set(REST_infoProvider, infoProvider);
+ public RestContextBuilder responseHandlers(Class<?>...responseHandlers) {
+ return addTo(REST_responseHandlers, responseHandlers);
}
/**
- * <b>Configuration property:</b> Static file mappings.
+ * <b>Configuration property:</b> Response handlers.
*
* <p>
- * Used to define paths and locations of statically-served files such as images or HTML documents.
- *
- * <p>
- * Static files are found by calling {@link RestContext#getClasspathResource(String,Locale)} which uses the registered
- * {@link ClasspathResourceFinder} for locating files on the classpath (or other location).
- *
- * <p>
- * An example where this class is used is in the {@link RestResource#staticFiles} annotation:
- * <p class='bcode'>
- * <jk>package</jk> com.foo.mypackage;
- *
- * <ja>@RestResource</ja>(
- * path=<js>"/myresource"</js>,
- * staticFiles=<js>"htdocs:docs"</js>
- * )
- * <jk>public class</jk> MyResource <jk>extends</jk> RestServletDefault {...}
- * </p>
- *
- * <p>
- * In the example above, given a GET request to <l>/myresource/htdocs/foobar.html</l>, the servlet will attempt to find
- * the <l>foobar.html</l> file in the following ordered locations:
- * <ol>
- * <li><l>com.foo.mypackage.docs</l> package.
- * <li><l>org.apache.juneau.rest.docs</l> package (since <l>RestServletDefault</l> is in <l>org.apache.juneau.rest</l>).
- * <li><l>[working-dir]/docs</l> directory.
- * </ol>
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_staticFiles}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#staticFiles()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#staticFiles(String)},
- * <li>{@link RestContextBuilder#staticFiles(Class,String)}
- * <li>{@link RestContextBuilder#staticFiles(String,String)}
- * <li>{@link RestContextBuilder#staticFiles(Class,String,String)}
- * <li>{@link RestContextBuilder#staticFiles(StaticFileMapping...)}
- * </ul>
- * <li>Mappings are cumulative from parent to child.
- * <li>Child resources can override mappings made on parent resources.
- * <li>The media type on the response is determined by the {@link RestContext#getMediaTypeForName(String)} method.
- * <li>The resource finder is configured via the {@link RestContext#REST_classpathResourceFinder} setting, and can be
- * overridden to provide customized handling of resource retrieval.
- * <li>The {@link RestContext#REST_useClasspathResourceCaching} setting can be used to cache static files in memory
- * to improve performance.
- * </ul>
+ * Same as {@link #responseHandlers(Class...)} except input is pre-constructed instances.
*
- * @param sfm The static file mappings to add to this resource.
- * @return This object (for method chaining).
- */
- public RestContextBuilder staticFiles(StaticFileMapping...sfm) {
- return addTo(REST_staticFiles, sfm);
- }
-
- /**
- * Same as {@link #staticFiles(StaticFileMapping...)}, except input is in the form of a mapping string.
- *
- * <p>
- * Mapping string must be one of these formats:
- * <ul>
- * <li><js>"path:location"</js> (e.g. <js>"foodocs:docs/foo"</js>)
- * <li><js>"path:location:headers-json"</js> (e.g. <js>"foodocs:docs/foo:{'Cache-Control':'max-age=86400, public'}"</js>)
- * </ul>
- *
- * @param mappingString The static file mapping string.
- * @return This object (for method chaining).
- */
- public RestContextBuilder staticFiles(String mappingString) {
- return staticFiles(new StaticFileMapping(resourceClass, mappingString));
- }
-
- /**
- * Same as {@link #staticFiles(String)}, except overrides the base class for retrieving the resource.
- *
- * <p>
- * Mapping string must be one of these formats:
- * <ul>
- * <li><js>"path:location"</js> (e.g. <js>"foodocs:docs/foo"</js>)
- * <li><js>"path:location:headers-json"</js> (e.g. <js>"foodocs:docs/foo:{'Cache-Control':'max-age=86400, public'}"</js>)
- * </ul>
- *
- * @param baseClass
- * Overrides the default class to use for retrieving the classpath resource.
- * <br>If <jk>null<jk>, uses the REST resource class.
- * @param mappingString The static file mapping string.
- * @return This object (for method chaining).
- */
- public RestContextBuilder staticFiles(Class<?> baseClass, String mappingString) {
- return staticFiles(new StaticFileMapping(baseClass, mappingString));
- }
-
- /**
- * Same as {@link #staticFiles(String)}, except path and location are already split values.
- *
- * @param path
- * The mapped URI path.
- * <br>Leading and trailing slashes are trimmed.
- * @param location
- * The location relative to the resource class.
- * <br>Leading and trailing slashes are trimmed.
- * @return This object (for method chaining).
- */
- public RestContextBuilder staticFiles(String path, String location) {
- return staticFiles(new StaticFileMapping(null, path, location, null));
- }
-
- /**
- * Same as {@link #staticFiles(String,String)}, except overrides the base class for retrieving the resource.
- *
- * @param baseClass
- * Overrides the default class to use for retrieving the classpath resource.
- * <br>If <jk>null<jk>, uses the REST resource class.
- * @param path
- * The mapped URI path.
- * <br>Leading and trailing slashes are trimmed.
- * @param location
- * The location relative to the resource class.
- * <br>Leading and trailing slashes are trimmed.
+ * @param responseHandlers The response handlers to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder staticFiles(Class<?> baseClass, String path, String location) {
- return staticFiles(new StaticFileMapping(baseClass, path, location, null));
+ public RestContextBuilder responseHandlers(ResponseHandler...responseHandlers) {
+ return addTo(REST_responseHandlers, responseHandlers);
}
/**
- * <b>Configuration property:</b> Messages.
- *
- * <p>
- * Identifies the location of the resource bundle for this class.
- *
- * <p>
- * This annotation is used to provide localized messages for the following methods:
- * <ul>
- * <li>{@link RestRequest#getMessage(String, Object...)}
- * <li>{@link RestContext#getMessages()}
- * </ul>
- *
- * <p>
- * Refer to the {@link MessageBundle} class for a description of the message key formats used in the properties file.
- *
- * <p>
- * The value can be a relative path like <js>"nls/Messages"</js>, indicating to look for the resource bundle
- * <js>"com.foo.sample.nls.Messages"</js> if the resource class is in <js>"com.foo.sample"</js>, or it can be an
- * absolute path, like <js>"com.foo.sample.nls.Messages"</js>
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_messages}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#messages()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#messages(String)},
- * <li>{@link RestContextBuilder#messages(Class,String)}
- * <li>{@link RestContextBuilder#messages(MessageBundleLocation)}
- * </ul>
- * <li>Mappings are cumulative from parent to child.
- * </ul>
- *
- * @param messageBundleLocation The message bundle location to add to the search path.
- * @return This object (for method chaining).
- */
- public RestContextBuilder messages(MessageBundleLocation messageBundleLocation) {
- return addTo(REST_messages, messageBundleLocation);
- }
-
- /**
- * <b>Configuration property:</b> Messages.
- *
- * <p>
- * Same as {@link #messages(MessageBundleLocation)} except allows you to pass in the base class and bundle
- * path separately.
- *
- * @param baseClass
- * The base class that the bundle path is relative to.
- * <br>If <jk>null</jk>, assumed to be the resource class itself.
- * @param bundlePath The bundle path relative to the base class.
- * @return This object (for method chaining).
- */
- public RestContextBuilder messages(Class<?> baseClass, String bundlePath) {
- return addTo(REST_messages, new MessageBundleLocation(baseClass, bundlePath));
- }
-
- /**
- * <b>Configuration property:</b> Messages.
- *
- * <p>
- * Same as {@link #messages(Class,String)} except assumes the base class is the resource class itself.
- *
- * @param bundlePath The bundle path relative to the base class.
- * @return This object (for method chaining).
- */
- public RestContextBuilder messages(String bundlePath) {
- return addTo(REST_messages, new MessageBundleLocation(null, bundlePath));
- }
-
- /**
- * <b>Configuration property:</b> Static file response headers.
- *
- * <p>
- * Used to customize the headers on responses returned for statically-served files.
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_staticFileResponseHeaders}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#staticFileResponseHeaders()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
- * <li>{@link RestContextBuilder#staticFileResponseHeaders(String...)}
- * <li>{@link RestContextBuilder#staticFileResponseHeader(String,String)}
- * </ul>
- * </ul>
- *
- * @param append
- * If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
- * @param headers The headers to add to this list.
- * @return This object (for method chaining).
- */
- public RestContextBuilder staticFileResponseHeaders(boolean append, Map<String,String> headers) {
- return set(append, REST_staticFileResponseHeaders, headers);
- }
-
- /**
- * <b>Configuration property:</b> Static file response headers.
- *
- * <p>
- * Used to customize the headers on responses returned for statically-served files.
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_staticFileResponseHeaders}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#staticFileResponseHeaders()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
- * <li>{@link RestContextBuilder#staticFileResponseHeaders(String...)}
- * <li>{@link RestContextBuilder#staticFileResponseHeader(String,String)}
- * </ul>
- * </ul>
- *
- * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
- * @return This object (for method chaining).
- * @throws RestServletException If malformed header is found.
- */
- public RestContextBuilder staticFileResponseHeaders(String...headers) throws RestServletException {
- for (String header : headers) {
- String[] h = RestUtils.parseHeader(header);
- if (h == null)
- throw new RestServletException("Invalid static file response header specified: ''{0}''. Must be in the format: ''Header-Name: header-value''", header);
- staticFileResponseHeader(h[0], h[1]);
- }
- return this;
- }
-
- /**
- * <b>Configuration property:</b> Static file response headers.
- *
- * <p>
- * Used to customize the headers on responses returned for statically-served files.
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_staticFileResponseHeaders}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#staticFileResponseHeaders()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
- * <li>{@link RestContextBuilder#staticFileResponseHeaders(String...)}
- * <li>{@link RestContextBuilder#staticFileResponseHeader(String,String)}
- * </ul>
- * </ul>
- *
- * @param name The HTTP header name.
- * @param value The HTTP header value.
- * @return This object (for method chaining).
- */
- public RestContextBuilder staticFileResponseHeader(String name, String value) {
- return addTo(REST_staticFileResponseHeaders, name, value);
- }
-
- /**
- * <b>Configuration property:</b> Classpath resource finder.
- *
- * <p>
- * Used to retrieve localized files from the classpath.
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_classpathResourceFinder}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#classpathResourceFinder()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#classpathResourceFinder(Class)}
- * <li>{@link RestContextBuilder#classpathResourceFinder(ClasspathResourceFinder)}
- * </ul>
- * <li>
- * The default value is {@link ClasspathResourceFinderBasic} which provides basic support for finding localized
- * resources on the classpath and JVM working directory.
- * <br>The {@link ClasspathResourceFinderRecursive} is another option that also recursively searches for resources
- * up the parent class hierarchy.
- * <br>Each of these classes can be extended to provide customized handling of resource retrieval.
- * </ul>
- *
- * @param classpathResourceFinder The resource finder class.
- * @return This object (for method chaining).
- */
- public RestContextBuilder classpathResourceFinder(Class<? extends ClasspathResourceFinder> classpathResourceFinder) {
- return set(REST_classpathResourceFinder, classpathResourceFinder);
- }
-
- /**
- * <b>Configuration property:</b> Classpath resource finder.
- *
- * <p>
- * Used to retrieve localized files from the classpath.
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_classpathResourceFinder}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#classpathResourceFinder()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#classpathResourceFinder(Class)}
- * <li>{@link RestContextBuilder#classpathResourceFinder(ClasspathResourceFinder)}
- * </ul>
- * <li>
- * The default value is {@link ClasspathResourceFinderBasic} which provides basic support for finding localized
- * resources on the classpath and JVM working directory.
- * <br>The {@link ClasspathResourceFinderRecursive} is another option that also recursively searches for resources
- * up the parent class hierarchy.
- * <br>Each of these classes can be extended to provide customized handling of resource retrieval.
- * </ul>
- *
- * @param classpathResourceFinder The resource finder instance.
- * @return This object (for method chaining).
- */
- public RestContextBuilder classpathResourceFinder(ClasspathResourceFinder classpathResourceFinder) {
- return set(REST_classpathResourceFinder, classpathResourceFinder);
- }
-
- /**
- * <b>Configuration property:</b> Use classpath resource caching.
- *
- * <p>
- * When enabled, resources retrieved via {@link RestContext#getClasspathResource(String, Locale)} (and related
- * methods) will be cached in memory to speed subsequent lookups.
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_useClasspathResourceCaching}
- * <li>Annotations:
- * <ul>
- * <li>{@link RestResource#useClasspathResourceCaching()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#useClasspathResourceCaching(boolean)}
- * </ul>
- * </ul>
- *
- * @param value The new value.
- * @return This object (for method chaining).
- */
- public RestContextBuilder useClasspathResourceCaching(boolean value) {
- return set(REST_useClasspathResourceCaching, value);
- }
-
- /**
- * <b>Configuration property:</b> HTML Widgets.
- *
- * <p>
- * Defines widgets that can be used in conjunction with string variables of the form <js>"$W{name}"</js>to quickly
- * generate arbitrary replacement text.
- *
- * Widgets resolve the following variables:
- * <ul>
- * <li><js>"$W{name}"</js> - Contents returned by {@link Widget#getHtml(RestRequest)}.
- * <li><js>"$W{name.script}"</js> - Contents returned by {@link Widget#getScript(RestRequest)}.
- * <br>The script contents are automatically inserted into the <xt><head/script></xt> section
- * in the HTML page.
- * <li><js>"$W{name.style}"</js> - Contents returned by {@link Widget#getStyle(RestRequest)}.
- * <br>The styles contents are automatically inserted into the <xt><head/style></xt> section
- * in the HTML page.
- * </ul>
- *
- * <p>
- * The following examples shows how to associate a widget with a REST method and then have it rendered in the links
- * and aside section of the page:
- *
- * <p class='bcode'>
- * <ja>@RestMethod</ja>(
- * widgets={
- * MyWidget.<jk>class</jk>
- * }
- * htmldoc=<ja>@HtmlDoc</ja>(
- * navlinks={
- * <js>"$W{MyWidget}"</js>
- * },
- * aside={
- * <js>"Check out this widget: $W{MyWidget}"</js>
- * }
- * )
- * )
- * </p>
- *
- * <h6 class='topic'>Notes:</h6>
- * <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_widgets}
- * <li>Annotations:
- * <ul>
- * <li>{@link HtmlDoc#widgets()}
- * </ul>
- * <li>Methods:
- * <ul>
- * <li>{@link RestContextBuilder#widgets(Class...)}
- * <li>{@link RestContextBuilder#widgets(Widget...)}
- * <li>{@link RestContextBuilder#widgets(boolean,Widget...)}
- * </ul>
- * <li>Widgets are inherited from parent to child, but can be overridden by reusing the widget name.
- * <li>Values are appended to the existing list.
- * </ul>
- *
- * @param values The widget classes to add.
- * @return This object (for method chaining).
- */
- @SuppressWarnings("unchecked")
- public RestContextBuilder widgets(Class<? extends Widget>...values) {
- return addTo(REST_widgets, values);
- }
-
- /**
- * <b>Configuration property:</b> HTML Widgets.
- *
- * <p>
- * Same as {@link #widgets(Class...)} but allows you to pass in pre-instantiated Widget objects.
- *
- * <p>
- * Values are appended to the existing list.
- *
- * @param values The widget objects to add.
- * @return This object (for method chaining).
- */
- public RestContextBuilder widgets(Widget...values) {
- return addTo(REST_widgets, values);
- }
-
- /**
- * <b>Configuration property:</b> HTML Widgets.
- *
- * <p>
- * Same as {@link #widgets(Class...)} but allows you to pass in pre-instantiated Widget objects.
- *
- * <p>
- * Values are appended to the existing list.
- *
- * @param append
- * If <jk>true</jk>, appends to the existing list of widgets.
- * <br>Otherwise, replaces the previous list.
- * @param values The widget objects to add.
- * @return This object (for method chaining).
- */
- public RestContextBuilder widgets(boolean append, Widget...values) {
- return set(append, REST_widgets, values);
- }
-
- /**
- * <b>Configuration property:</b> MIME types.
- *
- * <p>
- * Defines MIME-type file type mappings.
- *
- * <p>
- * Used for specifying the content type on file resources retrieved through the following methods:
- * <ul>
- * <li>{@link RestContext#resolveStaticFile(String)}
- * <li>{@link RestRequest#getClasspathReaderResource(String,boolean,MediaType)}
- * <li>{@link RestRequest#getClasspathReaderResource(String,boolean)}
- * <li>{@link RestRequest#getClasspathReaderResource(String)}
- * </ul>
- *
- * <p>
- * This list appends to the existing list provided by {@link ExtendedMimetypesFileTypeMap}.
+ * <b>Configuration property:</b> Serializer listener.
*
- * <h6 class='topic'>Notes:</h6>
+ * <p>
+ * Specifies the serializer listener class to use for listening to non-fatal serialization errors.
+ *
+ * <p>
+ * <h5 class='section'>Notes:</h5>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_mimeTypes}
- * <li>Annotations:
+ * <li>Property: {@link Serializer#SERIALIZER_listener}
+ * <li>Annotations:
* <ul>
- * <li>{@link RestResource#mimeTypes()}
+ * <li>{@link RestResource#serializerListener()}
* </ul>
- * <li>Methods:
+ * <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#mimeTypes(String...)}
+ * <li>{@link RestContextBuilder#serializerListener(Class)}
* </ul>
- * <li>Values are .mime.types formatted entry string.
- * <br>Example: <js>"image/svg+xml svg"</js>
- * </ul>
+ * </ul>
*
- * @param mimeTypes The MIME-types to add to this config.
+ * @param listener The listener to add to this config.
* @return This object (for method chaining).
*/
- public RestContextBuilder mimeTypes(String...mimeTypes) {
- return addTo(REST_mimeTypes, mimeTypes);
+ public RestContextBuilder serializerListener(Class<? extends SerializerListener> listener) {
+ return set(SERIALIZER_listener, listener);
}
/**
@@ -2362,8 +1889,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
* <b>Configuration property:</b> Serializers.
*
* <p>
- * Same as {@link #serializers(Class...)} except allows you to overwrite the list of existing serializers instead
- * of appending.
+ * Same as {@link #serializers(Class...)} except allows you to overwrite the previous value.
*
* @param append
* If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
@@ -2378,7 +1904,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
* <b>Configuration property:</b> Serializers.
*
* <p>
- * Same as {@link #serializers(Class...)} except allows you to pass in serializer instances.
+ * Same as {@link #serializers(Class...)} except input is pre-constructed instances.
*
* <p>
* Serializer instances are considered set-in-stone and do NOT inherit properties and transforms defined on the
@@ -2395,8 +1921,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
* <b>Configuration property:</b> Serializers.
*
* <p>
- * Same as {@link #serializers(Serializer...)} except allows you to overwrite the list of existing serializers instead
- * of appending.
+ * Same as {@link #serializers(Serializer...)} except allows you to overwrite the previous value.
*
* @param append
* If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
@@ -2408,336 +1933,464 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
}
/**
- * <b>Configuration property:</b> Parsers.
+ * <b>Configuration property:</b> Static file response headers.
*
* <p>
- * Adds class-level parsers to this resource.
+ * Used to customize the headers on responses returned for statically-served files.
*
* <h6 class='topic'>Notes:</h6>
* <ul class='spaced-list'>
- * <li>Property: {@link RestContext#REST_parsers}
+ * <li>Property: {@link RestContext#REST_staticFileResponseHeaders}
* <li>Annotations:
* <ul>
- * <li>{@link RestResource#parsers()}
- * <li>{@link RestMethod#parsers()}
+ * <li>{@link RestResource#staticFileResponseHeaders()}
* </ul>
* <li>Methods:
* <ul>
- * <li>{@link RestContextBuilder#parsers(Class...)}
- * <li>{@link RestContextBuilder#parsers(boolean,Class...)}
- * <li>{@link RestContextBuilder#parsers(Parser...)}
- * <li>{@link RestContextBuilder#parsers(boolean,Parser...)}
+ * <li>{@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
+ * <li>{@link RestContextBuilder#staticFileResponseHeaders(String...)}
+ * <li>{@link RestContextBuilder#staticFileResponseHeader(String,String)}
* </ul>
- * <li>When defined as a class, properties/transforms defined on the resource/method are inherited.
- * <li>When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
- * <li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
- * annotation.
* </ul>
+ *
+ * @param append
+ * If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value.
+ * @param headers The headers to add to this list.
+ * @return This object (for method chaining).
+ */
+ public RestContextBuilder staticFileResponseHeaders(boolean append, Map<String,String> headers) {
+ return set(append, REST_staticFileResponseHeaders, headers);
+ }
+
+ /**
+ * <b>Configuration property:</b> Static file response headers.
*
- * @param parsers The parser classes to add to this config.
+ * <p>
+ * Same as {@link #staticFileResponseHeaders(boolean, Map)} with append=<jk>true</jk> except headers are strings
+ * composed of key/value pairs.
+ *
+ * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
* @return This object (for method chaining).
+ * @throws RestServletException If malformed header is found.
*/
- public RestContextBuilder parsers(Class<?>...parsers) {
- return addTo(REST_parsers, parsers);
+ public RestContextBuilder staticFileResponseHeaders(String...headers) throws RestServletException {
+ for (String header : headers) {
+ String[] h = RestUtils.parseHeader(header);
+ if (h == null)
+ throw new RestServletException("Invalid static file response header specified: ''{0}''. Must be in the format: ''Header-Name: header-value''", header);
+ staticFileResponseHeader(h[0], h[1]);
+ }
+ return this;
}
/**
- * <b>Configuration property:</b> Parsers.
+ * <b>Configuration property:</b> Static file response headers.
*
* <p>
- * Same as {@
<TRUNCATED>
[2/3] juneau git commit: RestContext refactoring.
Posted by ja...@apache.org.
RestContext refactoring.
Project: http://git-wip-us.apache.org/repos/asf/juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/juneau/commit/76b634af
Tree: http://git-wip-us.apache.org/repos/asf/juneau/tree/76b634af
Diff: http://git-wip-us.apache.org/repos/asf/juneau/diff/76b634af
Branch: refs/heads/master
Commit: 76b634af0d7193f43f253f24c392db9eb8c5b228
Parents: b766e1d
Author: JamesBognar <ja...@apache.org>
Authored: Fri Jan 5 22:27:49 2018 -0500
Committer: JamesBognar <ja...@apache.org>
Committed: Fri Jan 5 22:27:49 2018 -0500
----------------------------------------------------------------------
.../apache/juneau/rest/RestContextBuilder.java | 2193 ++++++++----------
1 file changed, 923 insertions(+), 1270 deletions(-)
----------------------------------------------------------------------
[3/3] juneau git commit: RestContext refactoring.
Posted by ja...@apache.org.
RestContext refactoring.
Project: http://git-wip-us.apache.org/repos/asf/juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/juneau/commit/96fae4f9
Tree: http://git-wip-us.apache.org/repos/asf/juneau/tree/96fae4f9
Diff: http://git-wip-us.apache.org/repos/asf/juneau/diff/96fae4f9
Branch: refs/heads/master
Commit: 96fae4f9896b70ca2cea9bd4591f39736b96ecb5
Parents: 76b634a
Author: JamesBognar <ja...@apache.org>
Authored: Fri Jan 5 22:39:31 2018 -0500
Committer: JamesBognar <ja...@apache.org>
Committed: Fri Jan 5 22:39:31 2018 -0500
----------------------------------------------------------------------
.../java/org/apache/juneau/rest/CallMethod.java | 921 -------------------
.../java/org/apache/juneau/rest/CallRouter.java | 100 --
.../org/apache/juneau/rest/RestCallHandler.java | 12 +-
.../org/apache/juneau/rest/RestCallRouter.java | 100 ++
.../org/apache/juneau/rest/RestContext.java | 24 +-
.../apache/juneau/rest/RestInfoProvider.java | 6 +-
.../org/apache/juneau/rest/RestJavaMethod.java | 921 +++++++++++++++++++
7 files changed, 1042 insertions(+), 1042 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
deleted file mode 100644
index d33b60b..0000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
+++ /dev/null
@@ -1,921 +0,0 @@
-// ***************************************************************************************************************************
-// * 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 javax.servlet.http.HttpServletResponse.*;
-import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
-import static org.apache.juneau.internal.ClassUtils.*;
-import static org.apache.juneau.internal.StringUtils.*;
-import static org.apache.juneau.internal.Utils.*;
-import static org.apache.juneau.BeanContext.*;
-import static org.apache.juneau.rest.RestContext.*;
-
-import java.lang.annotation.*;
-import java.lang.reflect.*;
-import java.util.*;
-
-import javax.servlet.http.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.dto.swagger.*;
-import org.apache.juneau.encoders.*;
-import org.apache.juneau.http.*;
-import org.apache.juneau.httppart.*;
-import org.apache.juneau.httppart.HttpPartParser;
-import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.widget.*;
-import org.apache.juneau.serializer.*;
-import org.apache.juneau.svl.*;
-import org.apache.juneau.utils.*;
-
-/**
- * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
- */
-class CallMethod implements Comparable<CallMethod> {
- private final java.lang.reflect.Method method;
- private final String httpMethod;
- private final UrlPathPattern pathPattern;
- private final RestParam[] params;
- private final RestGuard[] guards;
- private final RestMatcher[] optionalMatchers;
- private final RestMatcher[] requiredMatchers;
- private final RestConverter[] converters;
- private final SerializerGroup serializers;
- private final ParserGroup parsers;
- private final EncoderGroup encoders;
- private final HttpPartParser partParser;
- private final HttpPartSerializer partSerializer;
- private final ObjectMap properties;
- private final Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
- private final String defaultCharset;
- private final long maxInput;
- private final boolean deprecated;
- private final String description, tags, summary, externalDocs;
- private final Integer priority;
- private final org.apache.juneau.rest.annotation.Parameter[] parameters;
- private final Response[] responses;
- private final RestContext context;
- private final BeanContext beanContext;
- private final Map<String,Widget> widgets;
- private final List<MediaType> supportedAcceptTypes, supportedContentTypes;
-
- CallMethod(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
- Builder b = new Builder(servlet, method, context);
- this.context = context;
- this.method = method;
- this.httpMethod = b.httpMethod;
- this.pathPattern = b.pathPattern;
- this.params = b.params;
- this.guards = b.guards;
- this.optionalMatchers = b.optionalMatchers;
- this.requiredMatchers = b.requiredMatchers;
- this.converters = b.converters;
- this.serializers = b.serializers;
- this.parsers = b.parsers;
- this.encoders = b.encoders;
- this.partParser = b.partParser;
- this.partSerializer = b.partSerializer;
- this.beanContext = b.beanContext;
- this.properties = b.properties;
- this.defaultRequestHeaders = b.defaultRequestHeaders;
- this.defaultQuery = b.defaultQuery;
- this.defaultFormData = b.defaultFormData;
- this.defaultCharset = b.defaultCharset;
- this.maxInput = b.maxInput;
- this.deprecated = b.deprecated;
- this.description = b.description;
- this.tags = b.tags;
- this.summary = b.summary;
- this.externalDocs = b.externalDocs;
- this.priority = b.priority;
- this.parameters = b.parameters;
- this.responses = b.responses;
- this.supportedAcceptTypes = b.supportedAcceptTypes;
- this.supportedContentTypes = b.supportedContentTypes;
- this.widgets = Collections.unmodifiableMap(b.widgets);
- }
-
- private static final class Builder {
- String httpMethod, defaultCharset, description, tags, summary, externalDocs;
- UrlPathPattern pathPattern;
- RestParam[] params;
- RestGuard[] guards;
- RestMatcher[] optionalMatchers, requiredMatchers;
- RestConverter[] converters;
- SerializerGroup serializers;
- ParserGroup parsers;
- EncoderGroup encoders;
- HttpPartParser partParser;
- HttpPartSerializer partSerializer;
- BeanContext beanContext;
- ObjectMap properties;
- Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
- boolean deprecated;
- long maxInput;
- Integer priority;
- org.apache.juneau.rest.annotation.Parameter[] parameters;
- Response[] responses;
- Map<String,Widget> widgets;
- List<MediaType> supportedAcceptTypes, supportedContentTypes;
-
- Builder(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
- String sig = method.getDeclaringClass().getName() + '.' + method.getName();
-
- try {
-
- RestMethod m = method.getAnnotation(RestMethod.class);
- if (m == null)
- throw new RestServletException("@RestMethod annotation not found on method ''{0}''", sig);
-
- if (! m.description().isEmpty())
- description = m.description();
- MethodSwagger sm = m.swagger();
- if (! sm.tags().isEmpty())
- tags = sm.tags();
- if (! m.summary().isEmpty())
- summary = m.summary();
- if (! sm.externalDocs().isEmpty())
- externalDocs = sm.externalDocs();
- deprecated = sm.deprecated();
- parameters = sm.parameters();
- responses = sm.responses();
- serializers = context.getSerializers();
- parsers = context.getParsers();
- partSerializer = context.getPartSerializer();
- partParser = context.getPartParser();
- beanContext = context.getBeanContext();
- encoders = context.getEncoders();
- properties = new ObjectMap().setInner(context.getProperties());
- defaultCharset = context.getDefaultCharset();
- maxInput = context.getMaxInput();
-
- if (! m.defaultCharset().isEmpty())
- defaultCharset = context.getVarResolver().resolve(m.defaultCharset());
- if (! m.maxInput().isEmpty())
- maxInput = StringUtils.parseLongWithSuffix(context.getVarResolver().resolve(m.maxInput()));
-
- HtmlDocBuilder hdb = new HtmlDocBuilder(properties);
-
- HtmlDoc hd = m.htmldoc();
- hdb.process(hd);
-
- widgets = new HashMap<>(context.getWidgets());
- for (Class<? extends Widget> wc : hd.widgets()) {
- Widget w = beanContext.newInstance(Widget.class, wc);
- widgets.put(w.getName(), w);
- hdb.script("INHERIT", "$W{"+w.getName()+".script}");
- hdb.style("INHERIT", "$W{"+w.getName()+".style}");
- }
-
- ASet<String> inherit = new ASet<String>().appendAll(StringUtils.split(m.inherit()));
- if (inherit.contains("*"))
- inherit.appendAll("SERIALIZERS","PARSERS","TRANSFORMS","PROPERTIES","ENCODERS");
-
- SerializerGroupBuilder sgb = null;
- ParserGroupBuilder pgb = null;
- ParserBuilder uepb = null;
- BeanContextBuilder bcb = null;
- PropertyStore cps = context.getPropertyStore();
-
- if (m.serializers().length > 0 || m.parsers().length > 0 || m.properties().length > 0 || m.flags().length > 0
- || m.beanFilters().length > 0 || m.pojoSwaps().length > 0 || m.bpi().length > 0
- || m.bpx().length > 0) {
- sgb = SerializerGroup.create();
- pgb = ParserGroup.create();
- uepb = Parser.create();
- bcb = beanContext.builder();
-
- if (inherit.contains("SERIALIZERS") || m.serializers().length == 0)
- sgb.append(cps.getArrayProperty(REST_serializers, Object.class));
-
- if (inherit.contains("PARSERS") || m.parsers().length == 0)
- pgb.append(cps.getArrayProperty(REST_parsers, Object.class));
- }
-
- httpMethod = m.name().toUpperCase(Locale.ENGLISH);
- if (httpMethod.equals("") && method.getName().startsWith("do"))
- httpMethod = method.getName().substring(2).toUpperCase(Locale.ENGLISH);
- if (httpMethod.equals(""))
- httpMethod = "GET";
- if (httpMethod.equals("METHOD"))
- httpMethod = "*";
-
- priority = m.priority();
-
- String p = m.path();
- converters = new RestConverter[m.converters().length];
- for (int i = 0; i < converters.length; i++)
- converters[i] = beanContext.newInstance(RestConverter.class, m.converters()[i]);
-
- guards = new RestGuard[m.guards().length];
- for (int i = 0; i < guards.length; i++)
- guards[i] = beanContext.newInstance(RestGuard.class, m.guards()[i]);
-
- List<RestMatcher> optionalMatchers = new LinkedList<>(), requiredMatchers = new LinkedList<>();
- for (int i = 0; i < m.matchers().length; i++) {
- Class<? extends RestMatcher> c = m.matchers()[i];
- RestMatcher matcher = beanContext.newInstance(RestMatcher.class, c, true, servlet, method);
- if (matcher.mustMatch())
- requiredMatchers.add(matcher);
- else
- optionalMatchers.add(matcher);
- }
- if (! m.clientVersion().isEmpty())
- requiredMatchers.add(new ClientVersionMatcher(context.getClientVersionHeader(), method));
-
- this.requiredMatchers = requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]);
- this.optionalMatchers = optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]);
-
- PropertyStore ps = context.getPropertyStore();
- if (! inherit.contains("TRANSFORMS"))
- ps = ps.builder().set(BEAN_beanFilters, null).set(BEAN_pojoSwaps, null).build();
-
- if (sgb != null) {
- sgb.append(m.serializers());
-
- if (! inherit.contains("PROPERTIES"))
- sgb.beanFilters(ps.getClassArrayProperty(BEAN_beanFilters)).pojoSwaps(ps.getClassArrayProperty(BEAN_pojoSwaps));
- else
- sgb.apply(ps);
- for (Property p1 : m.properties())
- sgb.set(p1.name(), p1.value());
- for (String p1 : m.flags())
- sgb.set(p1, true);
- if (m.bpi().length > 0) {
- Map<String,String> bpiMap = new LinkedHashMap<>();
- for (String s : m.bpi()) {
- for (String s2 : split(s, ';')) {
- int i = s2.indexOf(':');
- if (i == -1)
- throw new RestServletException(
- "Invalid format for @RestMethod.bpi() on method ''{0}''. Must be in the format \"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s);
- bpiMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
- }
- }
- sgb.includeProperties(bpiMap);
- }
- if (m.bpx().length > 0) {
- Map<String,String> bpxMap = new LinkedHashMap<>();
- for (String s : m.bpx()) {
- for (String s2 : split(s, ';')) {
- int i = s2.indexOf(':');
- if (i == -1)
- throw new RestServletException(
- "Invalid format for @RestMethod.bpx() on method ''{0}''. Must be in the format \"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s);
- bpxMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
- }
- }
- sgb.excludeProperties(bpxMap);
- }
- sgb.beanFilters(m.beanFilters());
- sgb.pojoSwaps(m.pojoSwaps());
- }
-
- if (pgb != null) {
- pgb.append(m.parsers());
- if (! inherit.contains("PROPERTIES"))
- pgb.beanFilters(ps.getClassArrayProperty(BEAN_beanFilters)).pojoSwaps(ps.getClassArrayProperty(BEAN_pojoSwaps));
- else
- pgb.apply(ps);
- for (Property p1 : m.properties())
- pgb.set(p1.name(), p1.value());
- for (String p1 : m.flags())
- pgb.set(p1, true);
- pgb.beanFilters(m.beanFilters());
- pgb.pojoSwaps(m.pojoSwaps());
- }
-
- if (uepb != null) {
- uepb.apply(ps);
- for (Property p1 : m.properties())
- uepb.set(p1.name(), p1.value());
- for (String p1 : m.flags())
- uepb.set(p1, true);
- uepb.beanFilters(m.beanFilters());
- uepb.pojoSwaps(m.pojoSwaps());
- }
-
- if (bcb != null) {
- bcb.apply(ps);
- for (Property p1 : m.properties())
- bcb.set(p1.name(), p1.value());
- for (String p1 : m.flags())
- bcb.set(p1, true);
- bcb.beanFilters(m.beanFilters());
- bcb.pojoSwaps(m.pojoSwaps());
- }
-
- if (m.properties().length > 0 || m.flags().length > 0) {
- properties = new ObjectMap().setInner(properties);
- for (Property p1 : m.properties())
- properties.put(p1.name(), p1.value());
- for (String p1 : m.flags())
- properties.put(p1, true);
- }
-
- if (m.encoders().length > 0) {
- EncoderGroupBuilder g = EncoderGroup.create().append(IdentityEncoder.INSTANCE);
- if (inherit.contains("ENCODERS"))
- g.append(encoders);
-
- for (Class<? extends Encoder> c : m.encoders()) {
- try {
- g.append(c);
- } catch (Exception e) {
- throw new RestServletException(
- "Exception occurred while trying to instantiate Encoder on method ''{0}'': ''{1}''", sig, c.getSimpleName()).initCause(e);
- }
- }
- encoders = g.build();
- }
-
- defaultRequestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- for (String s : m.defaultRequestHeaders()) {
- String[] h = RestUtils.parseKeyValuePair(s);
- if (h == null)
- throw new RestServletException(
- "Invalid default request header specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s);
- defaultRequestHeaders.put(h[0], h[1]);
- }
-
- defaultQuery = new LinkedHashMap<>();
- for (String s : m.defaultQuery()) {
- String[] h = RestUtils.parseKeyValuePair(s);
- if (h == null)
- throw new RestServletException(
- "Invalid default query parameter specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s);
- defaultQuery.put(h[0], h[1]);
- }
-
- defaultFormData = new LinkedHashMap<>();
- for (String s : m.defaultFormData()) {
- String[] h = RestUtils.parseKeyValuePair(s);
- if (h == null)
- throw new RestServletException(
- "Invalid default form data parameter specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s);
- defaultFormData.put(h[0], h[1]);
- }
-
- Type[] pt = method.getGenericParameterTypes();
- Annotation[][] pa = method.getParameterAnnotations();
- for (int i = 0; i < pt.length; i++) {
- for (Annotation a : pa[i]) {
- if (a instanceof Header) {
- Header h = (Header)a;
- if (! h.def().isEmpty())
- defaultRequestHeaders.put(firstNonEmpty(h.name(), h.value()), h.def());
- } else if (a instanceof Query) {
- Query q = (Query)a;
- if (! q.def().isEmpty())
- defaultQuery.put(firstNonEmpty(q.name(), q.value()), q.def());
- } else if (a instanceof FormData) {
- FormData f = (FormData)a;
- if (! f.def().isEmpty())
- defaultFormData.put(firstNonEmpty(f.name(), f.value()), f.def());
- }
- }
- }
-
- pathPattern = new UrlPathPattern(p);
-
- if (sgb != null)
- serializers = sgb.build();
- if (pgb != null)
- parsers = pgb.build();
- if (uepb != null && partParser instanceof Parser) {
- Parser pp = (Parser)partParser;
- partParser = (HttpPartParser)pp.builder().apply(uepb.getPropertyStore()).build();
- }
- if (bcb != null)
- beanContext = bcb.build();
-
- supportedAcceptTypes =
- m.supportedAcceptTypes().length > 0
- ? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(m.supportedAcceptTypes()))))
- : serializers.getSupportedMediaTypes();
- supportedContentTypes =
- m.supportedContentTypes().length > 0
- ? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(m.supportedContentTypes()))))
- : parsers.getSupportedMediaTypes();
-
- params = context.findParams(method, pathPattern, false);
-
- // Need this to access methods in anonymous inner classes.
- method.setAccessible(true);
- } catch (RestServletException e) {
- throw e;
- } catch (Exception e) {
- throw new RestServletException("Exception occurred while initializing method ''{0}''", sig).initCause(e);
- }
- }
- }
-
- /**
- * Returns <jk>true</jk> if this Java method has any guards or matchers.
- */
- boolean hasGuardsOrMatchers() {
- return (guards.length != 0 || requiredMatchers.length != 0 || optionalMatchers.length != 0);
- }
-
- /**
- * Returns the HTTP method name (e.g. <js>"GET"</js>).
- */
- String getHttpMethod() {
- return httpMethod;
- }
-
- /**
- * Returns the path pattern for this method.
- */
- String getPathPattern() {
- return pathPattern.toString();
- }
-
- /**
- * Returns the localized Swagger for this Java method.
- */
- Operation getSwaggerOperation(RestRequest req) throws ParseException {
- Operation o = operation()
- .operationId(method.getName())
- .description(getDescription(req))
- .tags(getTags(req))
- .summary(getSummary(req))
- .externalDocs(getExternalDocs(req))
- .parameters(getParameters(req))
- .responses(getResponses(req));
-
- if (isDeprecated())
- o.deprecated(true);
-
- if (! parsers.getSupportedMediaTypes().equals(context.getParsers().getSupportedMediaTypes()))
- o.consumes(parsers.getSupportedMediaTypes());
-
- if (! serializers.getSupportedMediaTypes().equals(context.getSerializers().getSupportedMediaTypes()))
- o.produces(serializers.getSupportedMediaTypes());
-
- return o;
- }
-
- private Operation getSwaggerOperationFromFile(RestRequest req) {
- Swagger s = req.getSwaggerFromFile();
- if (s != null && s.getPaths() != null && s.getPaths().get(pathPattern.getPatternString()) != null)
- return s.getPaths().get(pathPattern.getPatternString()).get(httpMethod);
- return null;
- }
-
- /**
- * Returns the localized summary for this Java method.
- */
- String getSummary(RestRequest req) {
- VarResolverSession vr = req.getVarResolverSession();
- if (summary != null)
- return vr.resolve(summary);
- String summary = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".summary");
- if (summary != null)
- return vr.resolve(summary);
- Operation o = getSwaggerOperationFromFile(req);
- if (o != null)
- return o.getSummary();
- return null;
- }
-
- /**
- * Returns the localized description for this Java method.
- */
- String getDescription(RestRequest req) {
- VarResolverSession vr = req.getVarResolverSession();
- if (description != null)
- return vr.resolve(description);
- String description = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".description");
- if (description != null)
- return vr.resolve(description);
- Operation o = getSwaggerOperationFromFile(req);
- if (o != null)
- return o.getDescription();
- return null;
- }
-
- /**
- * Returns the localized Swagger tags for this Java method.
- */
- private List<String> getTags(RestRequest req) {
- VarResolverSession vr = req.getVarResolverSession();
- JsonParser jp = JsonParser.DEFAULT;
- try {
- if (tags != null)
- return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
- String tags = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".tags");
- if (tags != null)
- return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
- Operation o = getSwaggerOperationFromFile(req);
- if (o != null)
- return o.getTags();
- return null;
- } catch (Exception e) {
- throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
- }
- }
-
- /**
- * Returns the localized Swagger external docs for this Java method.
- */
- private ExternalDocumentation getExternalDocs(RestRequest req) {
- VarResolverSession vr = req.getVarResolverSession();
- JsonParser jp = JsonParser.DEFAULT;
- try {
- if (externalDocs != null)
- return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
- String externalDocs = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".externalDocs");
- if (externalDocs != null)
- return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
- Operation o = getSwaggerOperationFromFile(req);
- if (o != null)
- return o.getExternalDocs();
- return null;
- } catch (Exception e) {
- throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
- }
- }
-
- /**
- * Returns the Swagger deprecated flag for this Java method.
- */
- private boolean isDeprecated() {
- return deprecated;
- }
-
- /**
- * Returns the localized Swagger parameter information for this Java method.
- */
- private List<ParameterInfo> getParameters(RestRequest req) throws ParseException {
- Operation o = getSwaggerOperationFromFile(req);
- if (o != null && o.getParameters() != null)
- return o.getParameters();
-
- VarResolverSession vr = req.getVarResolverSession();
- JsonParser jp = JsonParser.DEFAULT;
- Map<String,ParameterInfo> m = new TreeMap<>();
-
- // First parse @RestMethod.parameters() annotation.
- for (org.apache.juneau.rest.annotation.Parameter v : parameters) {
- String in = vr.resolve(v.in());
- ParameterInfo p = parameterInfo(in, vr.resolve(v.name()));
-
- if (! v.description().isEmpty())
- p.description(vr.resolve(v.description()));
- if (v.required())
- p.required(v.required());
-
- if ("body".equals(in)) {
- if (! v.schema().isEmpty())
- p.schema(jp.parse(vr.resolve(v.schema()), SchemaInfo.class));
- } else {
- if (v.allowEmptyValue())
- p.allowEmptyValue(v.allowEmptyValue());
- if (! v.collectionFormat().isEmpty())
- p.collectionFormat(vr.resolve(v.collectionFormat()));
- if (! v._default().isEmpty())
- p._default(vr.resolve(v._default()));
- if (! v.format().isEmpty())
- p.format(vr.resolve(v.format()));
- if (! v.items().isEmpty())
- p.items(jp.parse(vr.resolve(v.items()), Items.class));
- p.type(vr.resolve(v.type()));
- }
- m.put(p.getIn() + '.' + p.getName(), p);
- }
-
- // Next, look in resource bundle.
- String prefix = method.getName() + ".req";
- for (String key : context.getMessages().keySet(prefix)) {
- if (key.length() > prefix.length()) {
- String value = vr.resolve(context.getMessages().getString(key));
- String[] parts = key.substring(prefix.length() + 1).split("\\.");
- String in = parts[0], name, field;
- boolean isBody = "body".equals(in);
- if (parts.length == (isBody ? 2 : 3)) {
- if ("body".equals(in)) {
- name = null;
- field = parts[1];
- } else {
- name = parts[1];
- field = parts[2];
- }
- String k2 = in + '.' + name;
- ParameterInfo p = m.get(k2);
- if (p == null) {
- p = parameterInfoStrict(in, name);
- m.put(k2, p);
- }
-
- if (field.equals("description"))
- p.description(value);
- else if (field.equals("required"))
- p.required(Boolean.valueOf(value));
-
- if ("body".equals(in)) {
- if (field.equals("schema"))
- p.schema(jp.parse(value, SchemaInfo.class));
- } else {
- if (field.equals("allowEmptyValue"))
- p.allowEmptyValue(Boolean.valueOf(value));
- else if (field.equals("collectionFormat"))
- p.collectionFormat(value);
- else if (field.equals("default"))
- p._default(value);
- else if (field.equals("format"))
- p.format(value);
- else if (field.equals("items"))
- p.items(jp.parse(value, Items.class));
- else if (field.equals("type"))
- p.type(value);
- }
- } else {
- System.err.println("Unknown bundle key '"+key+"'");
- }
- }
- }
-
- // Finally, look for parameters defined on method.
- for (RestParam mp : this.params) {
- RestParamType in = mp.getParamType();
- if (in != RestParamType.OTHER) {
- String k2 = in.toString() + '.' + (in == RestParamType.BODY ? null : mp.getName());
- ParameterInfo p = m.get(k2);
- if (p == null) {
- p = parameterInfoStrict(in.toString(), mp.getName());
- m.put(k2, p);
- }
- }
- }
-
- if (m.isEmpty())
- return null;
- return new ArrayList<>(m.values());
- }
-
- /**
- * Returns the localized Swagger response information about this Java method.
- */
- private Map<Integer,ResponseInfo> getResponses(RestRequest req) throws ParseException {
- Operation o = getSwaggerOperationFromFile(req);
- if (o != null && o.getResponses() != null)
- return o.getResponses();
-
- VarResolverSession vr = req.getVarResolverSession();
- JsonParser jp = JsonParser.DEFAULT;
- Map<Integer,ResponseInfo> m = new TreeMap<>();
- Map<String,HeaderInfo> m2 = new TreeMap<>();
-
- // First parse @RestMethod.parameters() annotation.
- for (Response r : responses) {
- int httpCode = r.value();
- String description = r.description().isEmpty() ? RestUtils.getHttpResponseText(r.value()) : vr.resolve(r.description());
- ResponseInfo r2 = responseInfo(description);
-
- if (r.headers().length > 0) {
- for (org.apache.juneau.rest.annotation.Parameter v : r.headers()) {
- HeaderInfo h = headerInfoStrict(vr.resolve(v.type()));
- if (! v.collectionFormat().isEmpty())
- h.collectionFormat(vr.resolve(v.collectionFormat()));
- if (! v._default().isEmpty())
- h._default(vr.resolve(v._default()));
- if (! v.description().isEmpty())
- h.description(vr.resolve(v.description()));
- if (! v.format().isEmpty())
- h.format(vr.resolve(v.format()));
- if (! v.items().isEmpty())
- h.items(jp.parse(vr.resolve(v.items()), Items.class));
- r2.header(v.name(), h);
- m2.put(httpCode + '.' + v.name(), h);
- }
- }
- m.put(httpCode, r2);
- }
-
- // Next, look in resource bundle.
- String prefix = method.getName() + ".res";
- for (String key : context.getMessages().keySet(prefix)) {
- if (key.length() > prefix.length()) {
- String value = vr.resolve(context.getMessages().getString(key));
- String[] parts = key.substring(prefix.length() + 1).split("\\.");
- int httpCode = Integer.parseInt(parts[0]);
- ResponseInfo r2 = m.get(httpCode);
- if (r2 == null) {
- r2 = responseInfo(null);
- m.put(httpCode, r2);
- }
-
- String name = parts.length > 1 ? parts[1] : "";
-
- if ("header".equals(name) && parts.length > 3) {
- String headerName = parts[2];
- String field = parts[3];
-
- String k2 = httpCode + '.' + headerName;
- HeaderInfo h = m2.get(k2);
- if (h == null) {
- h = headerInfoStrict("string");
- m2.put(k2, h);
- r2.header(name, h);
- }
- if (field.equals("collectionFormat"))
- h.collectionFormat(value);
- else if (field.equals("default"))
- h._default(value);
- else if (field.equals("description"))
- h.description(value);
- else if (field.equals("format"))
- h.format(value);
- else if (field.equals("items"))
- h.items(jp.parse(value, Items.class));
- else if (field.equals("type"))
- h.type(value);
-
- } else if ("description".equals(name)) {
- r2.description(value);
- } else if ("schema".equals(name)) {
- r2.schema(jp.parse(value, SchemaInfo.class));
- } else if ("examples".equals(name)) {
- r2.examples(jp.parse(value, TreeMap.class));
- } else {
- System.err.println("Unknown bundle key '"+key+"'");
- }
- }
- }
-
- return m.isEmpty() ? null : m;
- }
-
- /**
- * Returns <jk>true</jk> if the specified request object can call this method.
- */
- boolean isRequestAllowed(RestRequest req) {
- for (RestGuard guard : guards) {
- req.setJavaMethod(method);
- if (! guard.isRequestAllowed(req))
- return false;
- }
- return true;
- }
-
- /**
- * Workhorse method.
- *
- * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
- * @return The HTTP response code.
- */
- int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
-
- String[] patternVals = pathPattern.match(pathInfo);
- if (patternVals == null)
- return SC_NOT_FOUND;
-
- String remainder = null;
- if (patternVals.length > pathPattern.getVars().length)
- remainder = patternVals[pathPattern.getVars().length];
- for (int i = 0; i < pathPattern.getVars().length; i++)
- req.getPathMatch().put(pathPattern.getVars()[i], patternVals[i]);
- req.getPathMatch().setRemainder(remainder);
-
- ObjectMap requestProperties = new ResolvingObjectMap(req.getVarResolverSession()).setInner(properties);
-
- req.init(method, requestProperties, defaultRequestHeaders, defaultQuery, defaultFormData, defaultCharset,
- maxInput, serializers, parsers, partParser, beanContext, encoders, widgets, supportedAcceptTypes, supportedContentTypes);
- res.init(requestProperties, defaultCharset, serializers, partSerializer, encoders);
-
- // Class-level guards
- for (RestGuard guard : context.getGuards())
- if (! guard.guard(req, res))
- return SC_UNAUTHORIZED;
-
- // If the method implements matchers, test them.
- for (RestMatcher m : requiredMatchers)
- if (! m.matches(req))
- return SC_PRECONDITION_FAILED;
- if (optionalMatchers.length > 0) {
- boolean matches = false;
- for (RestMatcher m : optionalMatchers)
- matches |= m.matches(req);
- if (! matches)
- return SC_PRECONDITION_FAILED;
- }
-
- context.preCall(req, res);
-
- Object[] args = new Object[params.length];
- for (int i = 0; i < params.length; i++) {
- try {
- args[i] = params[i].resolve(req, res);
- } catch (RestException e) {
- throw e;
- } catch (Exception e) {
- throw new RestException(SC_BAD_REQUEST,
- "Invalid data conversion. Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
- params[i].getParamType().name(), params[i].getName(), params[i].getType(), method.getDeclaringClass().getName(), method.getName()
- ).initCause(e);
- }
- }
-
- try {
-
- for (RestGuard guard : guards)
- if (! guard.guard(req, res))
- return SC_OK;
-
- Object output = method.invoke(context.getResource(), args);
- if (! method.getReturnType().equals(Void.TYPE))
- if (output != null || ! res.getOutputStreamCalled())
- res.setOutput(output);
-
- context.postCall(req, res);
-
- if (res.hasOutput()) {
- output = res.getOutput();
- for (RestConverter converter : converters)
- output = converter.convert(req, output, beanContext.getClassMetaForObject(output));
- res.setOutput(output);
- }
- } catch (IllegalArgumentException e) {
- throw new RestException(SC_BAD_REQUEST,
- "Invalid argument type passed to the following method: ''{0}''.\n\tArgument types: {1}",
- method.toString(), getReadableClassNames(args)
- ).initCause(e);
- } catch (InvocationTargetException e) {
- Throwable e2 = e.getTargetException(); // Get the throwable thrown from the doX() method.
- if (e2 instanceof RestException)
- throw (RestException)e2;
- if (e2 instanceof ParseException)
- throw new RestException(SC_BAD_REQUEST, e2);
- if (e2 instanceof InvalidDataConversionException)
- throw new RestException(SC_BAD_REQUEST, e2);
- throw new RestException(SC_INTERNAL_SERVER_ERROR, e2);
- } catch (RestException e) {
- throw e;
- } catch (Exception e) {
- throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
- }
- return SC_OK;
- }
-
- @Override /* Object */
- public String toString() {
- return "SimpleMethod: name=" + httpMethod + ", path=" + pathPattern.getPatternString();
- }
-
- /*
- * compareTo() method is used to keep SimpleMethods ordered in the CallRouter list.
- * It maintains the order in which matches are made during requests.
- */
- @Override /* Comparable */
- public int compareTo(CallMethod o) {
- int c;
-
- c = priority.compareTo(o.priority);
- if (c != 0)
- return c;
-
- c = pathPattern.compareTo(o.pathPattern);
- if (c != 0)
- return c;
-
- c = compare(o.requiredMatchers.length, requiredMatchers.length);
- if (c != 0)
- return c;
-
- c = compare(o.optionalMatchers.length, optionalMatchers.length);
- if (c != 0)
- return c;
-
- c = compare(o.guards.length, guards.length);
- if (c != 0)
- return c;
-
- return 0;
- }
-
- @Override /* Object */
- public boolean equals(Object o) {
- if (! (o instanceof CallMethod))
- return false;
- return (compareTo((CallMethod)o) == 0);
- }
-
- @Override /* Object */
- public int hashCode() {
- return super.hashCode();
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
deleted file mode 100644
index fc6c424..0000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// ***************************************************************************************************************************
-// * 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 javax.servlet.http.HttpServletResponse.*;
-
-import java.util.*;
-
-import javax.servlet.http.*;
-
-/**
- * Represents a group of CallMethods on a REST resource that handle the same HTTP Method name but with different
- * paths/matchers/guards/etc...
- *
- * <p>
- * Incoming requests for a particular HTTP method type (e.g. <js>"GET"</js>) are handed off to this class and then
- * dispatched to the appropriate CallMethod.
- */
-class CallRouter {
- private final CallMethod[] callMethods;
-
- CallRouter(CallMethod[] callMethods) {
- this.callMethods = callMethods;
- }
-
- /**
- * Builder class.
- */
- static final class Builder {
- private List<CallMethod> childMethods = new ArrayList<>();
- private Set<String> collisions = new HashSet<>();
- private String httpMethodName;
-
- Builder(String httpMethodName) {
- this.httpMethodName = httpMethodName;
- }
-
- String getHttpMethodName() {
- return httpMethodName;
- }
-
- Builder add(CallMethod m) throws RestServletException {
- if (! m.hasGuardsOrMatchers()) {
- String p = m.getHttpMethod() + ":" + m.getPathPattern();
- if (collisions.contains(p))
- throw new RestServletException("Duplicate Java methods assigned to the same method/pattern: ''{0}''", p);
- collisions.add(p);
- }
- childMethods.add(m);
- return this;
- }
-
- CallRouter build() {
- Collections.sort(childMethods);
- return new CallRouter(childMethods.toArray(new CallMethod[childMethods.size()]));
- }
- }
-
- /**
- * Workhorse method.
- *
- * <p>
- * Routes this request to one of the CallMethods.
- *
- * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
- * @return The HTTP response code.
- */
- int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
- if (callMethods.length == 1)
- return callMethods[0].invoke(pathInfo, req, res);
-
- int maxRc = 0;
- for (CallMethod m : callMethods) {
- int rc = m.invoke(pathInfo, req, res);
- if (rc == SC_OK)
- return SC_OK;
- maxRc = Math.max(maxRc, rc);
- }
- return maxRc;
- }
-
- @Override /* Object */
- public String toString() {
- StringBuilder sb = new StringBuilder("CallRouter: [\n");
- for (CallMethod sm : callMethods)
- sb.append("\t" + sm + "\n");
- sb.append("]");
- return sb.toString();
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
index 53e929e..adcebbb 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
@@ -44,7 +44,7 @@ public class RestCallHandler {
private final RestContext context;
private final RestLogger logger;
- private final Map<String,CallRouter> callRouters;
+ private final Map<String,RestCallRouter> restCallRouters;
/**
* Constructor.
@@ -54,7 +54,7 @@ public class RestCallHandler {
public RestCallHandler(RestContext context) {
this.context = context;
this.logger = context.getLogger();
- this.callRouters = context.getCallRouters();
+ this.restCallRouters = context.getCallRouters();
}
/**
@@ -153,10 +153,10 @@ public class RestCallHandler {
} else {
// If the specified method has been defined in a subclass, invoke it.
int rc = SC_METHOD_NOT_ALLOWED;
- if (callRouters.containsKey(methodUC)) {
- rc = callRouters.get(methodUC).invoke(pathInfo, req, res);
- } else if (callRouters.containsKey("*")) {
- rc = callRouters.get("*").invoke(pathInfo, req, res);
+ if (restCallRouters.containsKey(methodUC)) {
+ rc = restCallRouters.get(methodUC).invoke(pathInfo, req, res);
+ } else if (restCallRouters.containsKey("*")) {
+ rc = restCallRouters.get("*").invoke(pathInfo, req, res);
}
// If not invoked above, see if it's an OPTIONs request
http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
new file mode 100644
index 0000000..1cf1fc7
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
@@ -0,0 +1,100 @@
+// ***************************************************************************************************************************
+// * 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 javax.servlet.http.HttpServletResponse.*;
+
+import java.util.*;
+
+import javax.servlet.http.*;
+
+/**
+ * Represents a group of CallMethods on a REST resource that handle the same HTTP Method name but with different
+ * paths/matchers/guards/etc...
+ *
+ * <p>
+ * Incoming requests for a particular HTTP method type (e.g. <js>"GET"</js>) are handed off to this class and then
+ * dispatched to the appropriate RestJavaMethod.
+ */
+class RestCallRouter {
+ private final RestJavaMethod[] restJavaMethods;
+
+ RestCallRouter(RestJavaMethod[] callMethods) {
+ this.restJavaMethods = callMethods;
+ }
+
+ /**
+ * Builder class.
+ */
+ static final class Builder {
+ private List<RestJavaMethod> childMethods = new ArrayList<>();
+ private Set<String> collisions = new HashSet<>();
+ private String httpMethodName;
+
+ Builder(String httpMethodName) {
+ this.httpMethodName = httpMethodName;
+ }
+
+ String getHttpMethodName() {
+ return httpMethodName;
+ }
+
+ Builder add(RestJavaMethod m) throws RestServletException {
+ if (! m.hasGuardsOrMatchers()) {
+ String p = m.getHttpMethod() + ":" + m.getPathPattern();
+ if (collisions.contains(p))
+ throw new RestServletException("Duplicate Java methods assigned to the same method/pattern: ''{0}''", p);
+ collisions.add(p);
+ }
+ childMethods.add(m);
+ return this;
+ }
+
+ RestCallRouter build() {
+ Collections.sort(childMethods);
+ return new RestCallRouter(childMethods.toArray(new RestJavaMethod[childMethods.size()]));
+ }
+ }
+
+ /**
+ * Workhorse method.
+ *
+ * <p>
+ * Routes this request to one of the CallMethods.
+ *
+ * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
+ * @return The HTTP response code.
+ */
+ int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
+ if (restJavaMethods.length == 1)
+ return restJavaMethods[0].invoke(pathInfo, req, res);
+
+ int maxRc = 0;
+ for (RestJavaMethod m : restJavaMethods) {
+ int rc = m.invoke(pathInfo, req, res);
+ if (rc == SC_OK)
+ return SC_OK;
+ maxRc = Math.max(maxRc, rc);
+ }
+ return maxRc;
+ }
+
+ @Override /* Object */
+ public String toString() {
+ StringBuilder sb = new StringBuilder("RestCallRouter: [\n");
+ for (RestJavaMethod sm : restJavaMethods)
+ sb.append("\t" + sm + "\n");
+ sb.append("]");
+ return sb.toString();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 0810abc..7ab8ee7 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -1434,8 +1434,8 @@ public final class RestContext extends BeanContext {
private final MessageBundle msgs;
private final ConfigFile configFile;
private final VarResolver varResolver;
- private final Map<String,CallRouter> callRouters;
- private final Map<String,CallMethod> callMethods;
+ private final Map<String,RestCallRouter> callRouters;
+ private final Map<String,RestJavaMethod> callMethods;
private final Map<String,RestContext> childResources;
private final RestLogger logger;
private final RestCallHandler callHandler;
@@ -1589,8 +1589,8 @@ public final class RestContext extends BeanContext {
// Done after initializing fields above since we pass this object to the child resources.
//----------------------------------------------------------------------------------------------------
List<String> methodsFound = new LinkedList<>(); // Temporary to help debug transient duplicate method issue.
- Map<String,CallRouter.Builder> routers = new LinkedHashMap<>();
- Map<String,CallMethod> _javaRestMethods = new LinkedHashMap<>();
+ Map<String,RestCallRouter.Builder> routers = new LinkedHashMap<>();
+ Map<String,RestJavaMethod> _javaRestMethods = new LinkedHashMap<>();
Map<String,Method>
_startCallMethods = new LinkedHashMap<>(),
_preCallMethods = new LinkedHashMap<>(),
@@ -1617,7 +1617,7 @@ public final class RestContext extends BeanContext {
if (! Modifier.isPublic(method.getModifiers()))
throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", resourceClass.getName(), method.getName());
- CallMethod sm = new CallMethod(resource, method, this);
+ RestJavaMethod sm = new RestJavaMethod(resource, method, this);
String httpMethod = sm.getHttpMethod();
// PROXY is a special case where a method returns an interface that we
@@ -1630,7 +1630,7 @@ public final class RestContext extends BeanContext {
if (remoteableMethods.isEmpty())
throw new RestException(SC_INTERNAL_SERVER_ERROR, "Method {0} returns an interface {1} that doesn't define any remoteable methods.", getMethodSignature(method), interfaceClass.getReadableName());
- sm = new CallMethod(resource, method, this) {
+ sm = new RestJavaMethod(resource, method, this) {
@Override
int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
@@ -1769,8 +1769,8 @@ public final class RestContext extends BeanContext {
this.postInitChildFirstMethodParams = _postInitChildFirstMethodParams.toArray(new Class[_postInitChildFirstMethodParams.size()][]);
this.destroyMethodParams = _destroyMethodParams.toArray(new Class[_destroyMethodParams.size()][]);
- Map<String,CallRouter> _callRouters = new LinkedHashMap<>();
- for (CallRouter.Builder crb : routers.values())
+ Map<String,RestCallRouter> _callRouters = new LinkedHashMap<>();
+ for (RestCallRouter.Builder crb : routers.values())
_callRouters.put(crb.getHttpMethodName(), crb.build());
this.callRouters = Collections.unmodifiableMap(_callRouters);
@@ -1829,9 +1829,9 @@ public final class RestContext extends BeanContext {
}
}
- private static void addToRouter(Map<String, CallRouter.Builder> routers, String httpMethodName, CallMethod cm) throws RestServletException {
+ private static void addToRouter(Map<String, RestCallRouter.Builder> routers, String httpMethodName, RestJavaMethod cm) throws RestServletException {
if (! routers.containsKey(httpMethodName))
- routers.put(httpMethodName, new CallRouter.Builder(httpMethodName));
+ routers.put(httpMethodName, new RestCallRouter.Builder(httpMethodName));
routers.get(httpMethodName).add(cm);
}
@@ -2214,7 +2214,7 @@ public final class RestContext extends BeanContext {
*
* @return A map with HTTP method names upper-cased as the keys, and call routers as the values.
*/
- protected Map<String,CallRouter> getCallRouters() {
+ protected Map<String,RestCallRouter> getCallRouters() {
return callRouters;
}
@@ -2848,7 +2848,7 @@ public final class RestContext extends BeanContext {
*
* @return A map of Java method names to call method objects.
*/
- protected Map<String,CallMethod> getCallMethods() {
+ protected Map<String,RestJavaMethod> getCallMethods() {
return callMethods;
}
http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
index e40e26a..5bef97b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
@@ -141,7 +141,7 @@ public class RestInfoProvider {
.tags(getTags(req))
.externalDocs(getExternalDocs(req));
- for (CallMethod sm : context.getCallMethods().values()) {
+ for (RestJavaMethod sm : context.getCallMethods().values()) {
if (sm.isRequestAllowed(req)) {
Operation o = sm.getSwaggerOperation(req);
s.path(
@@ -207,7 +207,7 @@ public class RestInfoProvider {
* @return The localized summary of the method, or a blank string if no summary was found.
*/
public String getMethodSummary(String javaMethodName, RestRequest req) {
- CallMethod m = context.getCallMethods().get(javaMethodName);
+ RestJavaMethod m = context.getCallMethods().get(javaMethodName);
if (m != null)
return m.getSummary(req);
return "";
@@ -257,7 +257,7 @@ public class RestInfoProvider {
* @return The localized description of the method, or a blank string if no description was found.
*/
public String getMethodDescription(String javaMethodName, RestRequest req) {
- CallMethod m = context.getCallMethods().get(javaMethodName);
+ RestJavaMethod m = context.getCallMethods().get(javaMethodName);
if (m != null)
return m.getDescription(req);
return "";
http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
new file mode 100644
index 0000000..e172e69
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
@@ -0,0 +1,921 @@
+// ***************************************************************************************************************************
+// * 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 javax.servlet.http.HttpServletResponse.*;
+import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.Utils.*;
+import static org.apache.juneau.BeanContext.*;
+import static org.apache.juneau.rest.RestContext.*;
+
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.servlet.http.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.dto.swagger.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.httppart.HttpPartParser;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.widget.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
+ */
+class RestJavaMethod implements Comparable<RestJavaMethod> {
+ private final java.lang.reflect.Method method;
+ private final String httpMethod;
+ private final UrlPathPattern pathPattern;
+ private final RestParam[] params;
+ private final RestGuard[] guards;
+ private final RestMatcher[] optionalMatchers;
+ private final RestMatcher[] requiredMatchers;
+ private final RestConverter[] converters;
+ private final SerializerGroup serializers;
+ private final ParserGroup parsers;
+ private final EncoderGroup encoders;
+ private final HttpPartParser partParser;
+ private final HttpPartSerializer partSerializer;
+ private final ObjectMap properties;
+ private final Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
+ private final String defaultCharset;
+ private final long maxInput;
+ private final boolean deprecated;
+ private final String description, tags, summary, externalDocs;
+ private final Integer priority;
+ private final org.apache.juneau.rest.annotation.Parameter[] parameters;
+ private final Response[] responses;
+ private final RestContext context;
+ private final BeanContext beanContext;
+ private final Map<String,Widget> widgets;
+ private final List<MediaType> supportedAcceptTypes, supportedContentTypes;
+
+ RestJavaMethod(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
+ Builder b = new Builder(servlet, method, context);
+ this.context = context;
+ this.method = method;
+ this.httpMethod = b.httpMethod;
+ this.pathPattern = b.pathPattern;
+ this.params = b.params;
+ this.guards = b.guards;
+ this.optionalMatchers = b.optionalMatchers;
+ this.requiredMatchers = b.requiredMatchers;
+ this.converters = b.converters;
+ this.serializers = b.serializers;
+ this.parsers = b.parsers;
+ this.encoders = b.encoders;
+ this.partParser = b.partParser;
+ this.partSerializer = b.partSerializer;
+ this.beanContext = b.beanContext;
+ this.properties = b.properties;
+ this.defaultRequestHeaders = b.defaultRequestHeaders;
+ this.defaultQuery = b.defaultQuery;
+ this.defaultFormData = b.defaultFormData;
+ this.defaultCharset = b.defaultCharset;
+ this.maxInput = b.maxInput;
+ this.deprecated = b.deprecated;
+ this.description = b.description;
+ this.tags = b.tags;
+ this.summary = b.summary;
+ this.externalDocs = b.externalDocs;
+ this.priority = b.priority;
+ this.parameters = b.parameters;
+ this.responses = b.responses;
+ this.supportedAcceptTypes = b.supportedAcceptTypes;
+ this.supportedContentTypes = b.supportedContentTypes;
+ this.widgets = Collections.unmodifiableMap(b.widgets);
+ }
+
+ private static final class Builder {
+ String httpMethod, defaultCharset, description, tags, summary, externalDocs;
+ UrlPathPattern pathPattern;
+ RestParam[] params;
+ RestGuard[] guards;
+ RestMatcher[] optionalMatchers, requiredMatchers;
+ RestConverter[] converters;
+ SerializerGroup serializers;
+ ParserGroup parsers;
+ EncoderGroup encoders;
+ HttpPartParser partParser;
+ HttpPartSerializer partSerializer;
+ BeanContext beanContext;
+ ObjectMap properties;
+ Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
+ boolean deprecated;
+ long maxInput;
+ Integer priority;
+ org.apache.juneau.rest.annotation.Parameter[] parameters;
+ Response[] responses;
+ Map<String,Widget> widgets;
+ List<MediaType> supportedAcceptTypes, supportedContentTypes;
+
+ Builder(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
+ String sig = method.getDeclaringClass().getName() + '.' + method.getName();
+
+ try {
+
+ RestMethod m = method.getAnnotation(RestMethod.class);
+ if (m == null)
+ throw new RestServletException("@RestMethod annotation not found on method ''{0}''", sig);
+
+ if (! m.description().isEmpty())
+ description = m.description();
+ MethodSwagger sm = m.swagger();
+ if (! sm.tags().isEmpty())
+ tags = sm.tags();
+ if (! m.summary().isEmpty())
+ summary = m.summary();
+ if (! sm.externalDocs().isEmpty())
+ externalDocs = sm.externalDocs();
+ deprecated = sm.deprecated();
+ parameters = sm.parameters();
+ responses = sm.responses();
+ serializers = context.getSerializers();
+ parsers = context.getParsers();
+ partSerializer = context.getPartSerializer();
+ partParser = context.getPartParser();
+ beanContext = context.getBeanContext();
+ encoders = context.getEncoders();
+ properties = new ObjectMap().setInner(context.getProperties());
+ defaultCharset = context.getDefaultCharset();
+ maxInput = context.getMaxInput();
+
+ if (! m.defaultCharset().isEmpty())
+ defaultCharset = context.getVarResolver().resolve(m.defaultCharset());
+ if (! m.maxInput().isEmpty())
+ maxInput = StringUtils.parseLongWithSuffix(context.getVarResolver().resolve(m.maxInput()));
+
+ HtmlDocBuilder hdb = new HtmlDocBuilder(properties);
+
+ HtmlDoc hd = m.htmldoc();
+ hdb.process(hd);
+
+ widgets = new HashMap<>(context.getWidgets());
+ for (Class<? extends Widget> wc : hd.widgets()) {
+ Widget w = beanContext.newInstance(Widget.class, wc);
+ widgets.put(w.getName(), w);
+ hdb.script("INHERIT", "$W{"+w.getName()+".script}");
+ hdb.style("INHERIT", "$W{"+w.getName()+".style}");
+ }
+
+ ASet<String> inherit = new ASet<String>().appendAll(StringUtils.split(m.inherit()));
+ if (inherit.contains("*"))
+ inherit.appendAll("SERIALIZERS","PARSERS","TRANSFORMS","PROPERTIES","ENCODERS");
+
+ SerializerGroupBuilder sgb = null;
+ ParserGroupBuilder pgb = null;
+ ParserBuilder uepb = null;
+ BeanContextBuilder bcb = null;
+ PropertyStore cps = context.getPropertyStore();
+
+ if (m.serializers().length > 0 || m.parsers().length > 0 || m.properties().length > 0 || m.flags().length > 0
+ || m.beanFilters().length > 0 || m.pojoSwaps().length > 0 || m.bpi().length > 0
+ || m.bpx().length > 0) {
+ sgb = SerializerGroup.create();
+ pgb = ParserGroup.create();
+ uepb = Parser.create();
+ bcb = beanContext.builder();
+
+ if (inherit.contains("SERIALIZERS") || m.serializers().length == 0)
+ sgb.append(cps.getArrayProperty(REST_serializers, Object.class));
+
+ if (inherit.contains("PARSERS") || m.parsers().length == 0)
+ pgb.append(cps.getArrayProperty(REST_parsers, Object.class));
+ }
+
+ httpMethod = m.name().toUpperCase(Locale.ENGLISH);
+ if (httpMethod.equals("") && method.getName().startsWith("do"))
+ httpMethod = method.getName().substring(2).toUpperCase(Locale.ENGLISH);
+ if (httpMethod.equals(""))
+ httpMethod = "GET";
+ if (httpMethod.equals("METHOD"))
+ httpMethod = "*";
+
+ priority = m.priority();
+
+ String p = m.path();
+ converters = new RestConverter[m.converters().length];
+ for (int i = 0; i < converters.length; i++)
+ converters[i] = beanContext.newInstance(RestConverter.class, m.converters()[i]);
+
+ guards = new RestGuard[m.guards().length];
+ for (int i = 0; i < guards.length; i++)
+ guards[i] = beanContext.newInstance(RestGuard.class, m.guards()[i]);
+
+ List<RestMatcher> optionalMatchers = new LinkedList<>(), requiredMatchers = new LinkedList<>();
+ for (int i = 0; i < m.matchers().length; i++) {
+ Class<? extends RestMatcher> c = m.matchers()[i];
+ RestMatcher matcher = beanContext.newInstance(RestMatcher.class, c, true, servlet, method);
+ if (matcher.mustMatch())
+ requiredMatchers.add(matcher);
+ else
+ optionalMatchers.add(matcher);
+ }
+ if (! m.clientVersion().isEmpty())
+ requiredMatchers.add(new ClientVersionMatcher(context.getClientVersionHeader(), method));
+
+ this.requiredMatchers = requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]);
+ this.optionalMatchers = optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]);
+
+ PropertyStore ps = context.getPropertyStore();
+ if (! inherit.contains("TRANSFORMS"))
+ ps = ps.builder().set(BEAN_beanFilters, null).set(BEAN_pojoSwaps, null).build();
+
+ if (sgb != null) {
+ sgb.append(m.serializers());
+
+ if (! inherit.contains("PROPERTIES"))
+ sgb.beanFilters(ps.getClassArrayProperty(BEAN_beanFilters)).pojoSwaps(ps.getClassArrayProperty(BEAN_pojoSwaps));
+ else
+ sgb.apply(ps);
+ for (Property p1 : m.properties())
+ sgb.set(p1.name(), p1.value());
+ for (String p1 : m.flags())
+ sgb.set(p1, true);
+ if (m.bpi().length > 0) {
+ Map<String,String> bpiMap = new LinkedHashMap<>();
+ for (String s : m.bpi()) {
+ for (String s2 : split(s, ';')) {
+ int i = s2.indexOf(':');
+ if (i == -1)
+ throw new RestServletException(
+ "Invalid format for @RestMethod.bpi() on method ''{0}''. Must be in the format \"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s);
+ bpiMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
+ }
+ }
+ sgb.includeProperties(bpiMap);
+ }
+ if (m.bpx().length > 0) {
+ Map<String,String> bpxMap = new LinkedHashMap<>();
+ for (String s : m.bpx()) {
+ for (String s2 : split(s, ';')) {
+ int i = s2.indexOf(':');
+ if (i == -1)
+ throw new RestServletException(
+ "Invalid format for @RestMethod.bpx() on method ''{0}''. Must be in the format \"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s);
+ bpxMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
+ }
+ }
+ sgb.excludeProperties(bpxMap);
+ }
+ sgb.beanFilters(m.beanFilters());
+ sgb.pojoSwaps(m.pojoSwaps());
+ }
+
+ if (pgb != null) {
+ pgb.append(m.parsers());
+ if (! inherit.contains("PROPERTIES"))
+ pgb.beanFilters(ps.getClassArrayProperty(BEAN_beanFilters)).pojoSwaps(ps.getClassArrayProperty(BEAN_pojoSwaps));
+ else
+ pgb.apply(ps);
+ for (Property p1 : m.properties())
+ pgb.set(p1.name(), p1.value());
+ for (String p1 : m.flags())
+ pgb.set(p1, true);
+ pgb.beanFilters(m.beanFilters());
+ pgb.pojoSwaps(m.pojoSwaps());
+ }
+
+ if (uepb != null) {
+ uepb.apply(ps);
+ for (Property p1 : m.properties())
+ uepb.set(p1.name(), p1.value());
+ for (String p1 : m.flags())
+ uepb.set(p1, true);
+ uepb.beanFilters(m.beanFilters());
+ uepb.pojoSwaps(m.pojoSwaps());
+ }
+
+ if (bcb != null) {
+ bcb.apply(ps);
+ for (Property p1 : m.properties())
+ bcb.set(p1.name(), p1.value());
+ for (String p1 : m.flags())
+ bcb.set(p1, true);
+ bcb.beanFilters(m.beanFilters());
+ bcb.pojoSwaps(m.pojoSwaps());
+ }
+
+ if (m.properties().length > 0 || m.flags().length > 0) {
+ properties = new ObjectMap().setInner(properties);
+ for (Property p1 : m.properties())
+ properties.put(p1.name(), p1.value());
+ for (String p1 : m.flags())
+ properties.put(p1, true);
+ }
+
+ if (m.encoders().length > 0) {
+ EncoderGroupBuilder g = EncoderGroup.create().append(IdentityEncoder.INSTANCE);
+ if (inherit.contains("ENCODERS"))
+ g.append(encoders);
+
+ for (Class<? extends Encoder> c : m.encoders()) {
+ try {
+ g.append(c);
+ } catch (Exception e) {
+ throw new RestServletException(
+ "Exception occurred while trying to instantiate Encoder on method ''{0}'': ''{1}''", sig, c.getSimpleName()).initCause(e);
+ }
+ }
+ encoders = g.build();
+ }
+
+ defaultRequestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ for (String s : m.defaultRequestHeaders()) {
+ String[] h = RestUtils.parseKeyValuePair(s);
+ if (h == null)
+ throw new RestServletException(
+ "Invalid default request header specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s);
+ defaultRequestHeaders.put(h[0], h[1]);
+ }
+
+ defaultQuery = new LinkedHashMap<>();
+ for (String s : m.defaultQuery()) {
+ String[] h = RestUtils.parseKeyValuePair(s);
+ if (h == null)
+ throw new RestServletException(
+ "Invalid default query parameter specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s);
+ defaultQuery.put(h[0], h[1]);
+ }
+
+ defaultFormData = new LinkedHashMap<>();
+ for (String s : m.defaultFormData()) {
+ String[] h = RestUtils.parseKeyValuePair(s);
+ if (h == null)
+ throw new RestServletException(
+ "Invalid default form data parameter specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s);
+ defaultFormData.put(h[0], h[1]);
+ }
+
+ Type[] pt = method.getGenericParameterTypes();
+ Annotation[][] pa = method.getParameterAnnotations();
+ for (int i = 0; i < pt.length; i++) {
+ for (Annotation a : pa[i]) {
+ if (a instanceof Header) {
+ Header h = (Header)a;
+ if (! h.def().isEmpty())
+ defaultRequestHeaders.put(firstNonEmpty(h.name(), h.value()), h.def());
+ } else if (a instanceof Query) {
+ Query q = (Query)a;
+ if (! q.def().isEmpty())
+ defaultQuery.put(firstNonEmpty(q.name(), q.value()), q.def());
+ } else if (a instanceof FormData) {
+ FormData f = (FormData)a;
+ if (! f.def().isEmpty())
+ defaultFormData.put(firstNonEmpty(f.name(), f.value()), f.def());
+ }
+ }
+ }
+
+ pathPattern = new UrlPathPattern(p);
+
+ if (sgb != null)
+ serializers = sgb.build();
+ if (pgb != null)
+ parsers = pgb.build();
+ if (uepb != null && partParser instanceof Parser) {
+ Parser pp = (Parser)partParser;
+ partParser = (HttpPartParser)pp.builder().apply(uepb.getPropertyStore()).build();
+ }
+ if (bcb != null)
+ beanContext = bcb.build();
+
+ supportedAcceptTypes =
+ m.supportedAcceptTypes().length > 0
+ ? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(m.supportedAcceptTypes()))))
+ : serializers.getSupportedMediaTypes();
+ supportedContentTypes =
+ m.supportedContentTypes().length > 0
+ ? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(m.supportedContentTypes()))))
+ : parsers.getSupportedMediaTypes();
+
+ params = context.findParams(method, pathPattern, false);
+
+ // Need this to access methods in anonymous inner classes.
+ method.setAccessible(true);
+ } catch (RestServletException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RestServletException("Exception occurred while initializing method ''{0}''", sig).initCause(e);
+ }
+ }
+ }
+
+ /**
+ * Returns <jk>true</jk> if this Java method has any guards or matchers.
+ */
+ boolean hasGuardsOrMatchers() {
+ return (guards.length != 0 || requiredMatchers.length != 0 || optionalMatchers.length != 0);
+ }
+
+ /**
+ * Returns the HTTP method name (e.g. <js>"GET"</js>).
+ */
+ String getHttpMethod() {
+ return httpMethod;
+ }
+
+ /**
+ * Returns the path pattern for this method.
+ */
+ String getPathPattern() {
+ return pathPattern.toString();
+ }
+
+ /**
+ * Returns the localized Swagger for this Java method.
+ */
+ Operation getSwaggerOperation(RestRequest req) throws ParseException {
+ Operation o = operation()
+ .operationId(method.getName())
+ .description(getDescription(req))
+ .tags(getTags(req))
+ .summary(getSummary(req))
+ .externalDocs(getExternalDocs(req))
+ .parameters(getParameters(req))
+ .responses(getResponses(req));
+
+ if (isDeprecated())
+ o.deprecated(true);
+
+ if (! parsers.getSupportedMediaTypes().equals(context.getParsers().getSupportedMediaTypes()))
+ o.consumes(parsers.getSupportedMediaTypes());
+
+ if (! serializers.getSupportedMediaTypes().equals(context.getSerializers().getSupportedMediaTypes()))
+ o.produces(serializers.getSupportedMediaTypes());
+
+ return o;
+ }
+
+ private Operation getSwaggerOperationFromFile(RestRequest req) {
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null && s.getPaths() != null && s.getPaths().get(pathPattern.getPatternString()) != null)
+ return s.getPaths().get(pathPattern.getPatternString()).get(httpMethod);
+ return null;
+ }
+
+ /**
+ * Returns the localized summary for this Java method.
+ */
+ String getSummary(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ if (summary != null)
+ return vr.resolve(summary);
+ String summary = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".summary");
+ if (summary != null)
+ return vr.resolve(summary);
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null)
+ return o.getSummary();
+ return null;
+ }
+
+ /**
+ * Returns the localized description for this Java method.
+ */
+ String getDescription(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ if (description != null)
+ return vr.resolve(description);
+ String description = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".description");
+ if (description != null)
+ return vr.resolve(description);
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null)
+ return o.getDescription();
+ return null;
+ }
+
+ /**
+ * Returns the localized Swagger tags for this Java method.
+ */
+ private List<String> getTags(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ try {
+ if (tags != null)
+ return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
+ String tags = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".tags");
+ if (tags != null)
+ return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null)
+ return o.getTags();
+ return null;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Returns the localized Swagger external docs for this Java method.
+ */
+ private ExternalDocumentation getExternalDocs(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ try {
+ if (externalDocs != null)
+ return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
+ String externalDocs = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".externalDocs");
+ if (externalDocs != null)
+ return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null)
+ return o.getExternalDocs();
+ return null;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Returns the Swagger deprecated flag for this Java method.
+ */
+ private boolean isDeprecated() {
+ return deprecated;
+ }
+
+ /**
+ * Returns the localized Swagger parameter information for this Java method.
+ */
+ private List<ParameterInfo> getParameters(RestRequest req) throws ParseException {
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null && o.getParameters() != null)
+ return o.getParameters();
+
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ Map<String,ParameterInfo> m = new TreeMap<>();
+
+ // First parse @RestMethod.parameters() annotation.
+ for (org.apache.juneau.rest.annotation.Parameter v : parameters) {
+ String in = vr.resolve(v.in());
+ ParameterInfo p = parameterInfo(in, vr.resolve(v.name()));
+
+ if (! v.description().isEmpty())
+ p.description(vr.resolve(v.description()));
+ if (v.required())
+ p.required(v.required());
+
+ if ("body".equals(in)) {
+ if (! v.schema().isEmpty())
+ p.schema(jp.parse(vr.resolve(v.schema()), SchemaInfo.class));
+ } else {
+ if (v.allowEmptyValue())
+ p.allowEmptyValue(v.allowEmptyValue());
+ if (! v.collectionFormat().isEmpty())
+ p.collectionFormat(vr.resolve(v.collectionFormat()));
+ if (! v._default().isEmpty())
+ p._default(vr.resolve(v._default()));
+ if (! v.format().isEmpty())
+ p.format(vr.resolve(v.format()));
+ if (! v.items().isEmpty())
+ p.items(jp.parse(vr.resolve(v.items()), Items.class));
+ p.type(vr.resolve(v.type()));
+ }
+ m.put(p.getIn() + '.' + p.getName(), p);
+ }
+
+ // Next, look in resource bundle.
+ String prefix = method.getName() + ".req";
+ for (String key : context.getMessages().keySet(prefix)) {
+ if (key.length() > prefix.length()) {
+ String value = vr.resolve(context.getMessages().getString(key));
+ String[] parts = key.substring(prefix.length() + 1).split("\\.");
+ String in = parts[0], name, field;
+ boolean isBody = "body".equals(in);
+ if (parts.length == (isBody ? 2 : 3)) {
+ if ("body".equals(in)) {
+ name = null;
+ field = parts[1];
+ } else {
+ name = parts[1];
+ field = parts[2];
+ }
+ String k2 = in + '.' + name;
+ ParameterInfo p = m.get(k2);
+ if (p == null) {
+ p = parameterInfoStrict(in, name);
+ m.put(k2, p);
+ }
+
+ if (field.equals("description"))
+ p.description(value);
+ else if (field.equals("required"))
+ p.required(Boolean.valueOf(value));
+
+ if ("body".equals(in)) {
+ if (field.equals("schema"))
+ p.schema(jp.parse(value, SchemaInfo.class));
+ } else {
+ if (field.equals("allowEmptyValue"))
+ p.allowEmptyValue(Boolean.valueOf(value));
+ else if (field.equals("collectionFormat"))
+ p.collectionFormat(value);
+ else if (field.equals("default"))
+ p._default(value);
+ else if (field.equals("format"))
+ p.format(value);
+ else if (field.equals("items"))
+ p.items(jp.parse(value, Items.class));
+ else if (field.equals("type"))
+ p.type(value);
+ }
+ } else {
+ System.err.println("Unknown bundle key '"+key+"'");
+ }
+ }
+ }
+
+ // Finally, look for parameters defined on method.
+ for (RestParam mp : this.params) {
+ RestParamType in = mp.getParamType();
+ if (in != RestParamType.OTHER) {
+ String k2 = in.toString() + '.' + (in == RestParamType.BODY ? null : mp.getName());
+ ParameterInfo p = m.get(k2);
+ if (p == null) {
+ p = parameterInfoStrict(in.toString(), mp.getName());
+ m.put(k2, p);
+ }
+ }
+ }
+
+ if (m.isEmpty())
+ return null;
+ return new ArrayList<>(m.values());
+ }
+
+ /**
+ * Returns the localized Swagger response information about this Java method.
+ */
+ private Map<Integer,ResponseInfo> getResponses(RestRequest req) throws ParseException {
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null && o.getResponses() != null)
+ return o.getResponses();
+
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ Map<Integer,ResponseInfo> m = new TreeMap<>();
+ Map<String,HeaderInfo> m2 = new TreeMap<>();
+
+ // First parse @RestMethod.parameters() annotation.
+ for (Response r : responses) {
+ int httpCode = r.value();
+ String description = r.description().isEmpty() ? RestUtils.getHttpResponseText(r.value()) : vr.resolve(r.description());
+ ResponseInfo r2 = responseInfo(description);
+
+ if (r.headers().length > 0) {
+ for (org.apache.juneau.rest.annotation.Parameter v : r.headers()) {
+ HeaderInfo h = headerInfoStrict(vr.resolve(v.type()));
+ if (! v.collectionFormat().isEmpty())
+ h.collectionFormat(vr.resolve(v.collectionFormat()));
+ if (! v._default().isEmpty())
+ h._default(vr.resolve(v._default()));
+ if (! v.description().isEmpty())
+ h.description(vr.resolve(v.description()));
+ if (! v.format().isEmpty())
+ h.format(vr.resolve(v.format()));
+ if (! v.items().isEmpty())
+ h.items(jp.parse(vr.resolve(v.items()), Items.class));
+ r2.header(v.name(), h);
+ m2.put(httpCode + '.' + v.name(), h);
+ }
+ }
+ m.put(httpCode, r2);
+ }
+
+ // Next, look in resource bundle.
+ String prefix = method.getName() + ".res";
+ for (String key : context.getMessages().keySet(prefix)) {
+ if (key.length() > prefix.length()) {
+ String value = vr.resolve(context.getMessages().getString(key));
+ String[] parts = key.substring(prefix.length() + 1).split("\\.");
+ int httpCode = Integer.parseInt(parts[0]);
+ ResponseInfo r2 = m.get(httpCode);
+ if (r2 == null) {
+ r2 = responseInfo(null);
+ m.put(httpCode, r2);
+ }
+
+ String name = parts.length > 1 ? parts[1] : "";
+
+ if ("header".equals(name) && parts.length > 3) {
+ String headerName = parts[2];
+ String field = parts[3];
+
+ String k2 = httpCode + '.' + headerName;
+ HeaderInfo h = m2.get(k2);
+ if (h == null) {
+ h = headerInfoStrict("string");
+ m2.put(k2, h);
+ r2.header(name, h);
+ }
+ if (field.equals("collectionFormat"))
+ h.collectionFormat(value);
+ else if (field.equals("default"))
+ h._default(value);
+ else if (field.equals("description"))
+ h.description(value);
+ else if (field.equals("format"))
+ h.format(value);
+ else if (field.equals("items"))
+ h.items(jp.parse(value, Items.class));
+ else if (field.equals("type"))
+ h.type(value);
+
+ } else if ("description".equals(name)) {
+ r2.description(value);
+ } else if ("schema".equals(name)) {
+ r2.schema(jp.parse(value, SchemaInfo.class));
+ } else if ("examples".equals(name)) {
+ r2.examples(jp.parse(value, TreeMap.class));
+ } else {
+ System.err.println("Unknown bundle key '"+key+"'");
+ }
+ }
+ }
+
+ return m.isEmpty() ? null : m;
+ }
+
+ /**
+ * Returns <jk>true</jk> if the specified request object can call this method.
+ */
+ boolean isRequestAllowed(RestRequest req) {
+ for (RestGuard guard : guards) {
+ req.setJavaMethod(method);
+ if (! guard.isRequestAllowed(req))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Workhorse method.
+ *
+ * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
+ * @return The HTTP response code.
+ */
+ int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
+
+ String[] patternVals = pathPattern.match(pathInfo);
+ if (patternVals == null)
+ return SC_NOT_FOUND;
+
+ String remainder = null;
+ if (patternVals.length > pathPattern.getVars().length)
+ remainder = patternVals[pathPattern.getVars().length];
+ for (int i = 0; i < pathPattern.getVars().length; i++)
+ req.getPathMatch().put(pathPattern.getVars()[i], patternVals[i]);
+ req.getPathMatch().setRemainder(remainder);
+
+ ObjectMap requestProperties = new ResolvingObjectMap(req.getVarResolverSession()).setInner(properties);
+
+ req.init(method, requestProperties, defaultRequestHeaders, defaultQuery, defaultFormData, defaultCharset,
+ maxInput, serializers, parsers, partParser, beanContext, encoders, widgets, supportedAcceptTypes, supportedContentTypes);
+ res.init(requestProperties, defaultCharset, serializers, partSerializer, encoders);
+
+ // Class-level guards
+ for (RestGuard guard : context.getGuards())
+ if (! guard.guard(req, res))
+ return SC_UNAUTHORIZED;
+
+ // If the method implements matchers, test them.
+ for (RestMatcher m : requiredMatchers)
+ if (! m.matches(req))
+ return SC_PRECONDITION_FAILED;
+ if (optionalMatchers.length > 0) {
+ boolean matches = false;
+ for (RestMatcher m : optionalMatchers)
+ matches |= m.matches(req);
+ if (! matches)
+ return SC_PRECONDITION_FAILED;
+ }
+
+ context.preCall(req, res);
+
+ Object[] args = new Object[params.length];
+ for (int i = 0; i < params.length; i++) {
+ try {
+ args[i] = params[i].resolve(req, res);
+ } catch (RestException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RestException(SC_BAD_REQUEST,
+ "Invalid data conversion. Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
+ params[i].getParamType().name(), params[i].getName(), params[i].getType(), method.getDeclaringClass().getName(), method.getName()
+ ).initCause(e);
+ }
+ }
+
+ try {
+
+ for (RestGuard guard : guards)
+ if (! guard.guard(req, res))
+ return SC_OK;
+
+ Object output = method.invoke(context.getResource(), args);
+ if (! method.getReturnType().equals(Void.TYPE))
+ if (output != null || ! res.getOutputStreamCalled())
+ res.setOutput(output);
+
+ context.postCall(req, res);
+
+ if (res.hasOutput()) {
+ output = res.getOutput();
+ for (RestConverter converter : converters)
+ output = converter.convert(req, output, beanContext.getClassMetaForObject(output));
+ res.setOutput(output);
+ }
+ } catch (IllegalArgumentException e) {
+ throw new RestException(SC_BAD_REQUEST,
+ "Invalid argument type passed to the following method: ''{0}''.\n\tArgument types: {1}",
+ method.toString(), getReadableClassNames(args)
+ ).initCause(e);
+ } catch (InvocationTargetException e) {
+ Throwable e2 = e.getTargetException(); // Get the throwable thrown from the doX() method.
+ if (e2 instanceof RestException)
+ throw (RestException)e2;
+ if (e2 instanceof ParseException)
+ throw new RestException(SC_BAD_REQUEST, e2);
+ if (e2 instanceof InvalidDataConversionException)
+ throw new RestException(SC_BAD_REQUEST, e2);
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e2);
+ } catch (RestException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ return SC_OK;
+ }
+
+ @Override /* Object */
+ public String toString() {
+ return "SimpleMethod: name=" + httpMethod + ", path=" + pathPattern.getPatternString();
+ }
+
+ /*
+ * compareTo() method is used to keep SimpleMethods ordered in the RestCallRouter list.
+ * It maintains the order in which matches are made during requests.
+ */
+ @Override /* Comparable */
+ public int compareTo(RestJavaMethod o) {
+ int c;
+
+ c = priority.compareTo(o.priority);
+ if (c != 0)
+ return c;
+
+ c = pathPattern.compareTo(o.pathPattern);
+ if (c != 0)
+ return c;
+
+ c = compare(o.requiredMatchers.length, requiredMatchers.length);
+ if (c != 0)
+ return c;
+
+ c = compare(o.optionalMatchers.length, optionalMatchers.length);
+ if (c != 0)
+ return c;
+
+ c = compare(o.guards.length, guards.length);
+ if (c != 0)
+ return c;
+
+ return 0;
+ }
+
+ @Override /* Object */
+ public boolean equals(Object o) {
+ if (! (o instanceof RestJavaMethod))
+ return false;
+ return (compareTo((RestJavaMethod)o) == 0);
+ }
+
+ @Override /* Object */
+ public int hashCode() {
+ return super.hashCode();
+ }
+}
\ No newline at end of file