Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.juneau.rest.RestContext Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.rest;
import static jakarta.servlet.http.HttpServletResponse.*;
import static org.apache.juneau.collections.JsonMap.*;
import static org.apache.juneau.common.internal.IOUtils.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.http.HttpHeaders.*;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.rest.processor.ResponseProcessor.*;
import static java.util.Collections.*;
import static java.util.Optional.*;
import static org.apache.juneau.rest.annotation.RestOpAnnotation.*;
import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.lang.reflect.Method;
import java.nio.charset.*;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
import java.util.logging.*;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import org.apache.http.Header;
import org.apache.juneau.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.common.internal.*;
import org.apache.juneau.config.*;
import org.apache.juneau.config.vars.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.dto.swagger.Swagger;
import org.apache.juneau.encoders.*;
import org.apache.juneau.html.*;
import org.apache.juneau.html.annotation.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.httppart.HttpPartSerializer.Creator;
import org.apache.juneau.internal.*;
import org.apache.juneau.jsonschema.*;
import org.apache.juneau.oapi.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.parser.ParseException;
import org.apache.juneau.reflect.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.arg.*;
import org.apache.juneau.rest.debug.*;
import org.apache.juneau.rest.httppart.*;
import org.apache.juneau.rest.logger.*;
import org.apache.juneau.rest.processor.*;
import org.apache.juneau.rest.rrpc.*;
import org.apache.juneau.rest.servlet.*;
import org.apache.juneau.rest.staticfile.*;
import org.apache.juneau.rest.stats.*;
import org.apache.juneau.rest.swagger.*;
import org.apache.juneau.http.annotation.*;
import org.apache.juneau.http.header.*;
import org.apache.juneau.http.response.*;
import org.apache.juneau.rest.util.*;
import org.apache.juneau.rest.vars.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;
import org.apache.juneau.svl.vars.*;
import org.apache.juneau.utils.*;
/**
* Defines the initial configuration of a RestServlet or @Rest annotated object.
*
*
* An extension of the {@link ServletConfig} object used during servlet initialization.
*
*
* Methods are provided for overriding or augmenting the information provided by the @Rest annotation.
* In general, most information provided in the @Rest annotation can be specified programmatically
* through calls on this object.
*
*
* To interact with this object, simply pass it in as a constructor argument or in an INIT hook.
*
* // Option #1 - Pass in through constructor.
* public MyResource(RestContext.Builder builder ) {
* builder
* .swaps(TemporalCalendarSwap.IsoLocalDateTime.class );
* }
*
* // Option #2 - Use an init hook.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder
* .swaps(TemporalCalendarSwap.IsoLocalDateTime.class );
* }
*
*
* Notes:
* This class is thread safe and reusable.
*
*
* See Also:
*/
public class RestContext extends Context {
//-------------------------------------------------------------------------------------------------------------------
// Static
//-------------------------------------------------------------------------------------------------------------------
private static final Map, RestContext> REGISTRY = new ConcurrentHashMap<>();
/**
* Returns a registry of all created {@link RestContext} objects.
*
* @return An unmodifiable map of resource classes to {@link RestContext} objects.
*/
public static final Map, RestContext> getGlobalRegistry() {
return unmodifiable(REGISTRY);
}
/**
* Creates a new builder for this object.
*
* @param resourceClass
* The class annotated with @Rest .
* Must not be null .
* @param parentContext
* The parent context if the REST bean was registered via {@link Rest#children()}.
* Can be null if the bean is a top-level resource.
* @param servletConfig
* The servlet config passed into the servlet by the servlet container.
* Can be null if not available.
* If null , then some features (such as access to servlet init params) will not be available.
*
* @return A new builder object.
* @throws ServletException Something bad happened.
*/
public static Builder create(Class> resourceClass, RestContext parentContext, ServletConfig servletConfig) throws ServletException {
return new Builder(resourceClass, parentContext, servletConfig);
}
//-------------------------------------------------------------------------------------------------------------------
// Builder
//-------------------------------------------------------------------------------------------------------------------
/**
* Builder class.
*/
@FluentSetters(ignore={"set"})
public static final class Builder extends Context.Builder implements ServletConfig {
private static final Set> DELAYED_INJECTION = set(
BeanContext.Builder.class,
BeanStore.Builder.class,
BeanStore.class,
CallLogger.Builder.class,
CallLogger.class,
Config.class,
DebugEnablement.Builder.class,
DebugEnablement.class,
EncoderSet.Builder.class,
EncoderSet.class,
FileFinder.Builder.class,
FileFinder.class,
HttpPartParser.class,
HttpPartParser.Creator.class,
HttpPartSerializer.class,
HttpPartSerializer.Creator.class,
JsonSchemaGenerator.Builder.class,
JsonSchemaGenerator.class,
Logger.class,
Messages.Builder.class,
Messages.class,
MethodExecStore.Builder.class,
MethodExecStore.class,
ParserSet.Builder.class,
ParserSet.class,
ResponseProcessorList.Builder.class,
ResponseProcessorList.class,
RestChildren.Builder.class,
RestChildren.class,
RestOpArgList.Builder.class,
RestOpArgList.class,
RestOperations.Builder.class,
RestOperations.class,
SerializerSet.Builder.class,
SerializerSet.class,
StaticFiles.Builder.class,
StaticFiles.class,
SwaggerProvider.Builder.class,
SwaggerProvider.class,
ThrownStore.Builder.class,
ThrownStore.class,
VarList.class,
VarResolver.Builder.class,
VarResolver.class
);
private static final Set DELAYED_INJECTION_NAMES = set(
"defaultRequestAttributes",
"defaultRequestHeaders",
"defaultResponseHeaders",
"destroyMethods",
"endCallMethods",
"postCallMethods",
"postInitChildFirstMethods",
"postInitMethods",
"preCallMethods",
"startCallMethods"
);
//-----------------------------------------------------------------------------------------------------------------
// The following fields are meant to be modifiable.
// They should not be declared final.
// Read-only snapshots of these will be made in RestServletContext.
//-----------------------------------------------------------------------------------------------------------------
private boolean initialized;
ResourceSupplier resource;
ServletContext servletContext;
final ServletConfig inner;
final Class> resourceClass;
final RestContext parentContext;
private DefaultClassList defaultClasses;
private DefaultSettingsMap defaultSettings;
private BeanStore rootBeanStore, beanStore;
private Config config;
private VarResolver.Builder varResolver;
private Logger logger;
private ThrownStore.Builder thrownStore;
private MethodExecStore.Builder methodExecStore;
private Messages.Builder messages;
private ResponseProcessorList.Builder responseProcessors;
private BeanCreator callLogger;
private HttpPartSerializer.Creator partSerializer;
private HttpPartParser.Creator partParser;
private JsonSchemaGenerator.Builder jsonSchemaGenerator;
private BeanCreator staticFiles;
private HeaderList defaultRequestHeaders, defaultResponseHeaders;
private NamedAttributeMap defaultRequestAttributes;
private RestOpArgList.Builder restOpArgs;
private BeanCreator debugEnablement;
private MethodList startCallMethods, endCallMethods, postInitMethods, postInitChildFirstMethods, destroyMethods, preCallMethods, postCallMethods;
private RestOperations.Builder restOperations;
private RestChildren.Builder restChildren;
private BeanCreator swaggerProvider;
private BeanContext.Builder beanContext;
private EncoderSet.Builder encoders;
private SerializerSet.Builder serializers;
private ParserSet.Builder parsers;
String
allowedHeaderParams = env("RestContext.allowedHeaderParams", "Accept,Content-Type"),
allowedMethodHeaders = env("RestContext.allowedMethodHeaders", ""),
allowedMethodParams = env("RestContext.allowedMethodParams", "HEAD,OPTIONS"),
clientVersionHeader = env("RestContext.clientVersionHeader", "Client-Version"),
debugOn = env("RestContext.debugOn", null),
path = null,
uriAuthority = env("RestContext.uriAuthority", (String)null),
uriContext = env("RestContext.uriContext", (String)null);
UriRelativity uriRelativity = env("RestContext.uriRelativity", UriRelativity.RESOURCE);
UriResolution uriResolution = env("RestContext.uriResolution", UriResolution.ROOT_RELATIVE);
Charset defaultCharset = env("RestContext.defaultCharset", IOUtils.UTF8);
long maxInput = parseLongWithSuffix(env("RestContext.maxInput", "100M"));
List consumes, produces;
boolean disableContentParam = env("RestContext.disableContentParam", false);
boolean renderResponseStackTraces = env("RestContext.renderResponseStackTraces", false);
Class extends RestChildren> childrenClass = RestChildren.class;
Class extends RestOpContext> opContextClass = RestOpContext.class;
Class extends RestOperations> operationsClass = RestOperations.class;
List children = list();
/**
* Constructor.
*
* @param resourceClass
* The REST servlet/bean type that this context is defined against.
* @param parentContext The parent context if this is a child of another resource.
* @param servletConfig The servlet config if available.
*/
protected Builder(Class> resourceClass, RestContext parentContext, ServletConfig servletConfig) {
this.resourceClass = resourceClass;
this.inner = servletConfig;
this.parentContext = parentContext;
// Pass-through default values.
if (parentContext != null) {
defaultClasses = parentContext.defaultClasses.copy();
defaultSettings = parentContext.defaultSettings.copy();
rootBeanStore = parentContext.rootBeanStore;
} else {
defaultClasses = DefaultClassList.create();
defaultSettings = DefaultSettingsMap.create();
}
}
@Override /* Context.Builder */
public Builder copy() {
throw new NoSuchMethodError("Not implemented.");
}
@Override /* BeanContext.Builder */
public RestContext build() {
try {
return beanStore().createBean(RestContext.class).type(getType().orElse(RestContext.class)).builder(RestContext.Builder.class, this).run();
} catch (Exception e) {
throw new InternalServerError(e, "Could not instantiate RestContext.");
}
}
/**
* Performs initialization on this builder against the specified REST servlet/bean instance.
*
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return This object.
* @throws ServletException If hook method calls failed.
*/
public Builder init(Supplier> resource) throws ServletException {
if (initialized)
return this;
initialized = true;
this.resource = new ResourceSupplier(resourceClass, resource);
Supplier> r = this.resource;
Class> rc = resourceClass;
beanStore = createBeanStore(resource)
.build()
.addBean(Builder.class, this)
.addBean(ResourceSupplier.class, this.resource)
.addBean(ServletConfig.class, inner != null ? inner : this)
.addBean(ServletContext.class, (inner != null ? inner : this).getServletContext());
if (rootBeanStore == null) {
rootBeanStore = beanStore;
beanStore = BeanStore.of(rootBeanStore, r.get());
}
BeanStore bs = beanStore;
beanStore.add(BeanStore.class, bs);
varResolver = createVarResolver(bs, r, rc);
beanStore.add(VarResolver.class, varResolver.build());
config = beanStore.add(Config.class, createConfig(bs, r, rc));
beanStore.add(VarResolver.class, varResolver.bean(Config.class, config).build());
ClassInfo rci = ClassInfo.of(resourceClass);
// Get @RestInject fields initialized with values.
rci.forEachAllField(
x -> x.hasAnnotation(RestInject.class),
x -> x.getOptional(resource.get()).ifPresent(
y -> beanStore.add(
x.getType().inner(),
y,
RestInjectAnnotation.name(x.getAnnotation(RestInject.class))
)
)
);
rci.forEachMethod(x -> x.hasAnnotation(RestInject.class), x -> {
Class rt = x.getReturnType().inner();
String name = RestInjectAnnotation.name(x.getAnnotation(RestInject.class));
if (! (DELAYED_INJECTION.contains(rt) || DELAYED_INJECTION_NAMES.contains(name))) {
beanStore
.createMethodFinder(rt)
.find(Builder::isRestBeanMethod)
.run(y -> beanStore.add(rt, y, name));
}
});
VarResolverSession vrs = varResolver().build().createSession();
AnnotationWorkList work = AnnotationWorkList.of(vrs, rci.getAnnotationList(CONTEXT_APPLY_FILTER));
apply(work);
beanContext().apply(work);
partSerializer().apply(work);
partParser().apply(work);
jsonSchemaGenerator().apply(work);
runInitHooks(bs, resource());
// Set @RestInject fields not initialized with values.
rci.forEachAllField(
x -> x.hasAnnotation(RestInject.class),
x -> x.setIfNull(
resource.get(),
beanStore.getBean(
x.getType().inner(),
RestInjectAnnotation.name(x.getAnnotation(RestInject.class))
).orElse(null)
)
);
return this;
}
private void runInitHooks(BeanStore beanStore, Supplier> resource) throws ServletException {
Object r = resource.get();
Map map = map();
ClassInfo.ofProxy(r).forEachAllMethodParentFirst(
y -> y.hasAnnotation(RestInit.class) && ! y.hasArg(RestOpContext.Builder.class),
y -> {
String sig = y.getSignature();
if (! map.containsKey(sig))
map.put(sig, y.accessible());
}
);
for (MethodInfo m : map.values()) {
if (! beanStore.hasAllParams(m))
throw servletException("Could not call @RestInit method {0}.{1}. Could not find prerequisites: {2}.", m.getDeclaringClass().getSimpleName(), m.getSignature(), beanStore.getMissingParams(m));
try {
m.invoke(r, beanStore.getParams(m));
} catch (Exception e) {
throw servletException(e, "Exception thrown from @RestInit method {0}.{1}.", m.getDeclaringClass().getSimpleName(), m.getSignature());
}
}
}
/**
* Returns the REST servlet/bean instance that this context is defined against.
*
* @return The REST servlet/bean instance that this context is defined against.
*/
public Supplier> resource() {
return Objects.requireNonNull(resource, "Resource not available. init(Object) has not been called.");
}
/**
* Returns the REST servlet/bean instance that this context is defined against if it's the specified type.
*
* @param The expected type of the resource bean.
* @param type The expected type of the resource bean.
* @return The bean cast to that instance, or {@link Optional#empty()} if it's not the specified type.
*/
public Optional resourceAs(Class type) {
Object r = resource().get();
return optional(type.isInstance(r) ? type.cast(r) : null);
}
//-----------------------------------------------------------------------------------------------------------------
// defaultClasses
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default implementation class list.
*
*
* This defines the implementation classes for a variety of bean types.
*
*
* Default classes are inherited from the parent REST object.
* Typically used on the top-level {@link RestContext.Builder} to affect class types for that REST object and all children.
*
*
* Modifying the default class list on this builder does not affect the default class list on the parent builder, but changes made
* here are inherited by child builders.
*
* @return The default implementation class list.
*/
public DefaultClassList defaultClasses() {
return defaultClasses;
}
/**
* Adds to the default implementation class list.
*
*
* A shortcut for the following code:
*
*
* builder .defaultClasses().add(values );
*
*
* @param values The values to add to the list of default classes.
* @return This object.
* @see #defaultClasses()
*/
public Builder defaultClasses(Class>...values) {
defaultClasses().add(values);
return this;
}
//-----------------------------------------------------------------------------------------------------------------
// defaultSettings
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default settings map.
*
*
* Default settings are inherited from the parent REST object.
* Typically used on the top-level {@link RestContext.Builder} to affect settings for that REST object and all children.
*
*
* Modifying the default settings map on this builder does not affect the default settings on the parent builder, but changes made
* here are inherited by child builders.
*
* @return The default settings map.
*/
public DefaultSettingsMap defaultSettings() {
return defaultSettings;
}
/**
* Sets a value in the default settings map.
*
*
* A shortcut for the following code:
*
*
* builder .defaultSettings().add(key , value );
*
*
* @param key The setting key.
* @param value The setting value.
* @return This object.
* @see #defaultSettings()
*/
public Builder defaultSetting(String key, Object value) {
defaultSettings().set(key, value);
return this;
}
//-----------------------------------------------------------------------------------------------------------------
// beanStore
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the bean store in this builder.
*
*
* The bean store is a simple storage database for beans keyed by type and name.
*
*
* The bean store is created with the parent root bean store as the parent, allowing any beans in the root bean store to be available
* in this builder. The root bean store typically pulls from an injection framework such as Spring to allow injected beans to be used.
*
*
* The default bean store can be overridden via any of the following:
*
*
* @return The bean store in this builder.
*/
public BeanStore beanStore() {
return beanStore;
}
/**
* Adds a bean to the bean store of this class.
*
*
* Equivalent to calling:
*
* builder .beanStore().add(beanType , bean );
*
*
* See Also:
*
* @param The class to associate this bean with.
* @param beanType The class to associate this bean with.
* @param bean The bean. Can be null .
* @return This object.
*/
public Builder beanStore(Class beanType, T bean) {
beanStore().addBean(beanType, bean);
return this;
}
/**
* Adds a bean to the bean store of this class.
*
*
* Equivalent to calling:
*
* builder .beanStore().add(beanType , bean , name );
*
*
* See Also:
*
* @param The class to associate this bean with.
* @param beanType The class to associate this bean with.
* @param bean The bean. Can be null .
* @param name The bean name if this is a named bean. Can be null .
* @return This object.
*/
public Builder beanStore(Class beanType, T bean, String name) {
beanStore().addBean(beanType, bean, name);
return this;
}
/**
* Returns the root bean store.
*
*
* This is the bean store inherited from the parent resource and does not include
* any beans added by this class.
*
* @return The root bean store.
*/
public BeanStore rootBeanStore() {
return rootBeanStore;
}
/**
* Creates the bean store in this builder.
*
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new bean store builder.
*/
protected BeanStore.Builder createBeanStore(Supplier> resource) {
// Default value.
Value v = Value.of(
BeanStore
.create()
.parent(rootBeanStore())
.outer(resource.get())
);
// Apply @Rest(beanStore).
ClassInfo.of(resourceClass).forEachAnnotation(Rest.class, x -> isNotVoid(x.beanStore()), x -> v.get().type(x.beanStore()));
// Replace with bean from: @RestInject public [static] BeanStore xxx()
v.get().build()
.createMethodFinder(BeanStore.class)
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// varResolver
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the variable resolver sub-builder.
*
*
* The variable resolver is used to resolve string variables of the form "$X{...}" in various places such as annotations on the REST class and methods.
*
*
* Can be used to add more variables or context objects to the variable resolver.
* These variables affect the variable resolver returned by {@link RestRequest#getVarResolverSession()}.
*
*
* The var resolver is created by the constructor using the {@link #createVarResolver(BeanStore,Supplier,Class)} method and is initialized with the following variables:
*
* {@link ArgsVar}
* {@link CoalesceVar}
* {@link ConfigVar}
* {@link EnvVariablesVar}
* {@link FileVar}
* {@link HtmlWidgetVar}
* {@link IfVar}
* {@link LenVar}
* {@link LocalizationVar}
* {@link LowerCaseVar}
* {@link ManifestFileVar}
* {@link NotEmptyVar}
* {@link PatternExtractVar}
* {@link PatternMatchVar}
* {@link PatternReplaceVar}
* {@link RequestAttributeVar}
* {@link RequestFormDataVar}
* {@link RequestHeaderVar}
* {@link RequestPathVar}
* {@link RequestQueryVar}
* {@link RequestSwaggerVar}
* {@link RequestVar}
* {@link SerializedRequestAttrVar}
* {@link ServletInitParamVar}
* {@link SubstringVar}
* {@link SwaggerVar}
* {@link SwitchVar}
* {@link SystemPropertiesVar}
* {@link UpperCaseVar}
* {@link UrlEncodeVar}
* {@link UrlVar}
*
*
*
* The default var resolver can be overridden via any of the following:
*
*
* See Also:
*
* @return The variable resolver sub-builder.
*/
public VarResolver.Builder varResolver() {
return varResolver;
}
/**
* Adds one or more variables to the var resolver of this class.
*
*
* Equivalent to calling:
*
* builder .vars().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
@SafeVarargs
public final Builder vars(Class extends Var>...value) {
varResolver.vars(value);
return this;
}
/**
* Adds one or more variables to the var resolver of this class.
*
*
* Equivalent to calling:
*
* builder .vars().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
public Builder vars(Var...value) {
varResolver.vars(value);
return this;
}
/**
* Creates the variable resolver sub-builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @param resourceClass
* The REST servlet/bean type that this context is defined against.
* @return A new variable resolver sub-builder.
*/
protected VarResolver.Builder createVarResolver(BeanStore beanStore, Supplier> resource, Class> resourceClass) {
// Default value.
Value v = Value.of(
VarResolver
.create()
.defaultVars()
.vars(
VarList.of(
ConfigVar.class,
FileVar.class,
LocalizationVar.class,
RequestAttributeVar.class,
RequestFormDataVar.class,
RequestHeaderVar.class,
RequestPathVar.class,
RequestQueryVar.class,
RequestVar.class,
RequestSwaggerVar.class,
SerializedRequestAttrVar.class,
ServletInitParamVar.class,
SwaggerVar.class,
UrlVar.class,
UrlEncodeVar.class,
HtmlWidgetVar.class
)
.addDefault()
)
.bean(FileFinder.class, FileFinder.create(beanStore).cp(resourceClass,null,true).build())
);
// Replace with bean from bean store.
beanStore
.getBean(VarResolver.class)
.ifPresent(x -> v.get().impl(x));
// Replace with bean from: @RestInject public [static] VarResolver xxx()
beanStore
.createMethodFinder(VarResolver.class)
.addBean(VarResolver.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// config
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the external configuration file for this resource.
*
*
* The config file contains arbitrary configuration information that can be accessed by this class, usually
* via $C variables.
*
*
* The default config can be overridden via any of the following:
*
*
*
* If a config file is not set up, then an empty config file will be returned that is not backed by any file.
*
*
* This bean can be accessed directly via {@link RestContext#getConfig()} or passed in as a parameter
* on a {@link RestOp}-annotated method.
*
*
See Also:
*
* @return The external configuration file for this resource.
*/
public Config config() {
return config;
}
/**
* Overwrites the default config file with a custom config file.
*
*
* By default, the config file is determined using the {@link Rest#config() @Rest(config)}
* annotation.
* This method allows you to programmatically override it with your own custom config file.
*
*
See Also:
*
* @param config The new config file.
* @return This object.
*/
@FluentSetter
public Builder config(Config config) {
this.config = config;
return this;
}
/**
* Creates the config for this builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @param resourceClass
* The REST servlet/bean type that this context is defined against.
* @return A new config.
*/
protected Config createConfig(BeanStore beanStore, Supplier> resource, Class> resourceClass) {
Value v = Value.empty();
// Find our config file. It's the last non-empty @RestResource(config).
VarResolver vr = beanStore.getBean(VarResolver.class).orElseThrow(() -> new IllegalArgumentException("VarResolver not found."));
Value cfv = Value.empty();
ClassInfo.of(resourceClass).forEachAnnotation(Rest.class, x -> isNotEmpty(x.config()), x -> cfv.set(vr.resolve(x.config())));
String cf = cfv.orElse("");
// If not specified or value is set to SYSTEM_DEFAULT, use system default config.
if (v.isEmpty() && "SYSTEM_DEFAULT".equals(cf))
v.set(Config.getSystemDefault());
// Otherwise build one.
if (v.isEmpty()) {
Config.Builder cb = Config.create().varResolver(vr);
if (! cf.isEmpty())
cb.name(cf);
v.set(cb.build());
}
// Replace with bean from bean store.
beanStore
.getBean(Config.class)
.ifPresent(x -> v.set(x));
// Replace with bean from: @RestInject public [static] Config xxx()
beanStore
.createMethodFinder(Config.class)
.addBean(Config.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// logger
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the logger for this resource.
*
*
* The logger is used in the following locations:
*
* {@link RestServlet#log(Level, Throwable, String, Object...)} and related methods.
* {@link RestObject#log(Level, Throwable, String, Object...)} and related methods.
* In the {@link #callLogger()} of this resource.
*
* It can also be accessed directly via {@link RestContext#getLogger()} or passed in as a parameter
* on a {@link RestOp}-annotated method.
*
*
* The default config can be overridden via any of the following:
*
*
* See Also:
*
* @return The logger for this resource.
*/
public Logger logger() {
if (logger == null)
logger = createLogger(beanStore(), resource, resourceClass);
return logger;
}
/**
* Sets the logger for this resource.
*
* See Also:
*
* @param value The logger to use for the REST resource.
* @return This object.
*/
public Builder logger(Logger value) {
logger = value;
return this;
}
/**
* Instantiates the logger for this resource.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @param resourceClass
* The REST servlet/bean class that this context is defined against.
* @return A new logger.
*/
protected Logger createLogger(BeanStore beanStore, Supplier> resource, Class> resourceClass) {
// Default value.
Value v = Value.of(
Logger.getLogger(resourceClass.getClass().getName())
);
// Replace with bean from bean store.
beanStore
.getBean(Logger.class)
.ifPresent(x -> v.set(x));
// Replace with bean from: @RestInject public [static] Logger xxx()
beanStore
.createMethodFinder(Logger.class)
.addBean(Logger.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// thrownStore
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the thrown-store sub-builder.
*
*
* The thrown store is an in-memory cache of thrown exceptions.
* It is used to store thrown exceptions when {@link MethodExecStats#error(Throwable)} is called from the {@link MethodExecStore}
* bean of this resource. It can also be accessed directly via {@link RestContext#getThrownStore()} or passed in as a parameter
* on a {@link RestOp}-annotated method.
*
*
* The default thrown store is inherited from the parent context and can be overridden via any of the following:
*
*
* See Also:
*
* @return The builder for the {@link ThrownStore} object in the REST context.
*/
public ThrownStore.Builder thrownStore() {
if (thrownStore == null)
thrownStore = createThrownStore(beanStore(), resource(), parentContext);
return thrownStore;
}
/**
* Specifies the thrown store for this class.
*
*
* Equivalent to calling:
*
* builder .thrownStore().type(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder thrownStore(Class extends ThrownStore> value) {
thrownStore().type(value);
return this;
}
/**
* Specifies the thrown store for this class.
*
*
* Equivalent to calling:
*
* builder .thrownStore().impl(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder thrownStore(ThrownStore value) {
thrownStore().impl(value);
return this;
}
/**
* Instantiates the thrown-store sub-builder.
*
* See Also:
*
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @param parent
* The parent context if the REST bean was registered via {@link Rest#children()}.
* Will be null if the bean is a top-level resource.
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* Created by {@link RestContext.Builder#beanStore()}.
* @return A new thrown-store sub-builder.
*/
protected ThrownStore.Builder createThrownStore(BeanStore beanStore, Supplier> resource, RestContext parent) {
// Default value.
Value v = Value.of(
ThrownStore
.create(beanStore)
.impl(parent == null ? null : parent.getThrownStore())
);
// Specify the implementation class if its set as a default.
defaultClasses()
.get(ThrownStore.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from bean store.
beanStore
.getBean(ThrownStore.class)
.ifPresent(x->v.get().impl(x));
// Replace with bean from: @RestInject public [static] ThrownStore xxx()
beanStore
.createMethodFinder(ThrownStore.class)
.addBean(ThrownStore.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// encoders
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the encoder group sub-builder.
*
*
* Encoders are used to decode HTTP requests and encode HTTP responses based on {@code Content-Encoding} and {@code Accept-Encoding}
* headers.
*
*
* The default encoder set has support for identity incoding only.
* It can be overridden via any of the following:
*
*
* See Also:
*
* @return The builder for the {@link EncoderSet} object in the REST context.
*/
public EncoderSet.Builder encoders() {
if (encoders == null)
encoders = createEncoders(beanStore(), resource());
return encoders;
}
/**
* Adds one or more encoders to this class.
*
*
* Equivalent to calling:
*
* builder .encoders().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
@SafeVarargs
public final Builder encoders(Class extends Encoder>...value) {
encoders().add(value);
return this;
}
/**
* Adds one or more encoders to this class.
*
*
* Equivalent to calling:
*
* builder .encoders().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
public Builder encoders(Encoder...value) {
encoders().add(value);
return this;
}
/**
* Instantiates the encoder group sub-builder.
*
* See Also:
*
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* Created by {@link RestContext.Builder#beanStore()}.
* @return A new encoder group sub-builder.
*/
protected EncoderSet.Builder createEncoders(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
EncoderSet
.create(beanStore)
.add(IdentityEncoder.INSTANCE)
);
// Specify the implementation class if its set as a default.
defaultClasses()
.get(EncoderSet.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from bean store.
beanStore
.getBean(EncoderSet.class)
.ifPresent(x->v.get().impl(x));
// Replace with bean from: @RestInject public [static] EncoderSet xxx()
beanStore
.createMethodFinder(EncoderSet.class)
.addBean(EncoderSet.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// serializers
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the serializer group sub-builder.
*
*
* Serializers are used to convert POJOs to HTTP response bodies based on the {@code Accept} header.
*
*
* The default serializer set is empty.
* It can be overridden via any of the following:
*
*
* See Also:
*
* @return The serializer group sub-builder.
*/
public SerializerSet.Builder serializers() {
if (serializers == null)
serializers = createSerializers(beanStore(), resource());
return serializers;
}
/**
* Adds one or more serializers to this class.
*
*
* Equivalent to calling:
*
* builder .serializers().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
@SafeVarargs
public final Builder serializers(Class extends Serializer>...value) {
serializers().add(value);
return this;
}
/**
* Adds one or more serializers to this class.
*
*
* Equivalent to calling:
*
* builder .serializers().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
public Builder serializers(Serializer...value) {
serializers().add(value);
return this;
}
/**
* Instantiates the serializer group sub-builder.
*
* See Also:
*
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* Created by {@link RestContext.Builder#beanStore()}.
* @return A new serializer group sub-builder.
*/
protected SerializerSet.Builder createSerializers(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
SerializerSet
.create(beanStore)
);
// Specify the implementation class if its set as a default.
defaultClasses()
.get(SerializerSet.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from bean store.
beanStore
.getBean(SerializerSet.class)
.ifPresent(x->v.get().impl(x));
// Replace with bean from: @RestInject public [static] SerializerSet xxx()
beanStore
.createMethodFinder(SerializerSet.class)
.addBean(SerializerSet.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// parsers
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the parser group sub-builder.
*
*
* Parsers are used to HTTP request bodies into POJOs based on the {@code Content-Type} header.
*
*
* The default parser set is empty.
* It can be overridden via any of the following:
*
*
* See Also:
*
* @return The parser group sub-builder.
*/
public ParserSet.Builder parsers() {
if (parsers == null)
parsers = createParsers(beanStore(), resource());
return parsers;
}
/**
* Adds one or more parsers to this class.
*
*
* Equivalent to calling:
*
* builder .parsers().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
@SafeVarargs
public final Builder parsers(Class extends Parser>...value) {
parsers().add(value);
return this;
}
/**
* Adds one or more parsers to this class.
*
*
* Equivalent to calling:
*
* builder .parsers().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
public Builder parsers(Parser...value) {
parsers().add(value);
return this;
}
/**
* Instantiates the parser group sub-builder.
*
* See Also:
*
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* Created by {@link RestContext.Builder#beanStore()}.
* @return A new parser group sub-builder.
*/
protected ParserSet.Builder createParsers(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
ParserSet
.create(beanStore)
);
// Specify the implementation class if its set as a default.
defaultClasses()
.get(ParserSet.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from bean store.
beanStore
.getBean(ParserSet.class)
.ifPresent(x->v.get().impl(x));
// Replace with bean from: @RestInject public [static] ParserSet xxx()
beanStore
.createMethodFinder(ParserSet.class)
.addBean(ParserSet.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// methodExecStore
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the method execution statistics store sub-builder.
*
*
* Used for tracking basic call statistics on Java methods in this class.
* It can be accessed directly via {@link RestContext#getMethodExecStore()} or passed in as a parameter
* on a {@link RestOp}-annotated method.
*
*
* The default method exec store can overridden via any of the following:
*
*
* See Also:
*
* @return The method execution statistics store sub-builder.
*/
public MethodExecStore.Builder methodExecStore() {
if (methodExecStore == null)
methodExecStore = createMethodExecStore(beanStore(), resource());
return methodExecStore;
}
/**
* Specifies the method execution store for this class.
*
*
* Equivalent to calling:
*
* builder .methodExecStore().type(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder methodExecStore(Class extends MethodExecStore> value) {
methodExecStore().type(value);
return this;
}
/**
* Specifies the method execution store for this class.
*
*
* Equivalent to calling:
*
* builder .methodExecStore().impl(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder methodExecStore(MethodExecStore value) {
methodExecStore().impl(value);
return this;
}
/**
* Instantiates the method execution statistics store sub-builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new method execution statistics store sub-builder.
*/
protected MethodExecStore.Builder createMethodExecStore(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
MethodExecStore
.create(beanStore)
);
// Specify the implementation class if its set as a default.
defaultClasses()
.get(MethodExecStore.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from bean store.
beanStore
.getBean(MethodExecStore.class)
.ifPresent(x->v.get().impl(x));
// Replace with bean from: @RestInject public [static] MethodExecStore xxx()
beanStore
.createMethodFinder(MethodExecStore.class)
.addBean(MethodExecStore.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// messages
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the messages sub-builder.
*
*
* Messages beans are wrappers around resource bundles containing localized messages.
*
*
* By default, the resource bundle name is assumed to match the class name. For example, given the class
* MyClass.java , the resource bundle is assumed to be MyClass.properties . This property
* allows you to override this setting to specify a different location such as MyMessages.properties by
* specifying a value of "MyMessages" .
*
*
* Resource bundles are searched using the following base name patterns:
*
* "{package}.{name}"
* "{package}.i18n.{name}"
* "{package}.nls.{name}"
* "{package}.messages.{name}"
*
*
*
* This annotation is used to provide request-localized (based on Accept-Language ) messages for the following methods:
*
* {@link RestRequest#getMessage(String, Object...)}
* {@link RestContext#getMessages() RestContext.getMessages()}
*
*
*
* Request-localized messages are also available by passing either of the following parameter types into your Java method:
*
* {@link ResourceBundle} - Basic Java resource bundle.
* {@link Messages} - Extended resource bundle with several convenience methods.
*
*
* The value can be a relative path like "nls/Messages" , indicating to look for the resource bundle
* "com.foo.sample.nls.Messages" if the resource class is in "com.foo.sample" , or it can be an
* absolute path like "com.foo.sample.nls.Messages"
*
* Examples:
*
* # Contents of org/apache/foo/nls/MyMessages.properties
*
* HelloMessage = Hello {0}!
*
*
* // Contents of org/apache/foo/MyResource.java
*
* @Rest (messages="nls/MyMessages" )
* public class MyResource {...}
*
* @RestGet ("/hello/{you}" )
* public Object helloYou(RestRequest req , Messages messages , @Path ("name" ) String you ) {
* String string ;
*
* // Get it from the RestRequest object.
* string = req .getMessage("HelloMessage" , you );
*
* // Or get it from the method parameter.
* string = messages .getString("HelloMessage" , you );
*
* // Or get the message in a locale different from the request.
* string = messages .forLocale(Locale.UK ).getString("HelloMessage" , you );
*
* return string ;
* }
* }
*
*
*
* The default messages can overridden via any of the following:
*
*
* Notes:
* Mappings are cumulative from super classes.
* Therefore, you can find and retrieve messages up the class-hierarchy chain.
*
*
* See Also:
*
* @return The messages sub-builder.
*/
public Messages.Builder messages() {
if (messages == null)
messages = createMessages(beanStore(), resource());
return messages;
}
/**
* Specifies the messages bundle for this class.
*
*
* Equivalent to calling:
*
* builder .messages().type(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder messages(Class extends Messages> value) {
messages().type(value);
return this;
}
/**
* Specifies the messages bundle for this class.
*
*
* Equivalent to calling:
*
* builder .messages().impl(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder messages(Messages value) {
messages().impl(value);
return this;
}
/**
* Instantiates the messages sub-builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new messages sub-builder.
*/
protected Messages.Builder createMessages(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
Messages
.create(resourceClass)
);
// Replace with bean from bean store.
beanStore
.getBean(Messages.class)
.ifPresent(x->v.get().impl(x));
// Replace with bean from: @RestInject public [static] Messages xxx()
beanStore
.createMethodFinder(Messages.class)
.addBean(Messages.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// responseProcessors
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the response processor list sub-builder.
*
*
* Specifies a list of {@link ResponseProcessor} classes that know how to convert POJOs returned by REST methods or
* set via {@link RestResponse#setContent(Object)} into appropriate HTTP responses.
*
*
* By default, the following response handlers are provided in the specified order:
*
* {@link ReaderProcessor}
* {@link InputStreamProcessor}
* {@link ThrowableProcessor}
* {@link HttpResponseProcessor}
* {@link HttpResourceProcessor}
* {@link HttpEntityProcessor}
* {@link ResponseBeanProcessor}
* {@link PlainTextPojoProcessor}
* {@link SerializedPojoProcessor}
*
*
* Example:
*
* // Our custom response processor for Foo objects.
* public class MyResponseProcessor implements ResponseProcessor {
*
* @Override
* public int process(RestOpSession opSession ) throws IOException {
*
* RestResponse res = opSession .getResponse();
* Foo foo = res .getOutput(Foo.class );
*
* if (foo == null )
* return NEXT ; // Let the next processor handle it.
*
* try (Writer writer = res .getNegotiatedWriter()) {
* //Pipe it to the writer ourselves.
* }
*
* return FINISHED ; // We handled it.
* }
* }
* }
*
* // Option #1 - Defined via annotation.
* @Rest (responseProcessors=MyResponseProcessor.class )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .responseProcessors(MyResponseProcessors.class );
* }
*
* @RestGet (...)
* public Object myMethod() {
* // Return a special object for our handler.
* return new MySpecialObject();
* }
* }
*
*
*
* The default response processors can overridden via any of the following:
*
*
* Notes:
*
* Response processors are always inherited from ascendant resources.
*
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(RestContext)
* public T()
* public static T create (RestContext)
* public static T create ()
*
*
* Inner classes of the REST resource class are allowed.
*
*
* See Also:
*
* @return The response processor list sub-builder.
*/
public ResponseProcessorList.Builder responseProcessors() {
if (responseProcessors == null)
responseProcessors = createResponseProcessors(beanStore(), resource());
return responseProcessors;
}
/**
* Adds one or more response processors to this class.
*
*
* Equivalent to calling:
*
* builder .responseProcessors().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
@SafeVarargs
public final Builder responseProcessors(Class extends ResponseProcessor>...value) {
responseProcessors().add(value);
return this;
}
/**
* Adds one or more response processors to this class.
*
*
* Equivalent to calling:
*
* builder .responseProcessors().add(value );
*
*
* See Also:
*
* @param value The values to add.
* @return This object.
*/
public Builder responseProcessors(ResponseProcessor...value) {
responseProcessors().add(value);
return this;
}
/**
* Instantiates the response processor list sub-builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new response processor list sub-builder.
*/
protected ResponseProcessorList.Builder createResponseProcessors(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
ResponseProcessorList
.create(beanStore)
.add(
ReaderProcessor.class,
InputStreamProcessor.class,
ThrowableProcessor.class,
HttpResponseProcessor.class,
HttpResourceProcessor.class,
HttpEntityProcessor.class,
ResponseBeanProcessor.class,
PlainTextPojoProcessor.class,
SerializedPojoProcessor.class
)
);
// Replace with bean from bean store.
beanStore
.getBean(ResponseProcessorList.class)
.ifPresent(x -> v.get().impl(x));
// Replace with bean from: @RestInject public [static] ResponseProcessorList xxx()
beanStore
.createMethodFinder(ResponseProcessorList.class)
.addBean(ResponseProcessorList.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// callLogger
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the call logger bean creator.
*
*
* Specifies the logger to use for logging of HTTP requests and responses.
*
*
Example:
*
* // Our customized logger.
* public class MyLogger extends BasicCallLogger {
*
* public MyLogger(BeanStore beanStore ) {
* super (beanStore );
* }
*
* @Override
* protected void log(Level level , String msg , Throwable e ) {
* // Handle logging ourselves.
* }
* }
*
* // Option #1 - Registered via annotation resolving to a config file setting with default value.
* @Rest (callLogger=MyLogger.class )
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .callLogger(MyLogger.class );
* }
* }
*
*
*
* The default call logger can overridden via any of the following:
*
*
* Notes:
*
* The default call logger if not specified is {@link BasicCallLogger}.
*
* The resource class itself will be used if it implements the {@link CallLogger} interface and not
* explicitly overridden via this annotation.
*
* When defined as a class, the implementation must have one of the following constructor:
*
*
* Inner classes of the REST resource class are allowed.
*
*
* See Also:
*
* @return The call logger sub-builder.
* @throws RuntimeException If {@link #init(Supplier)} has not been called.
*/
public BeanCreator callLogger() {
if (callLogger == null)
callLogger = createCallLogger(beanStore, resource);
return callLogger;
}
/**
* Specifies the call logger for this class.
*
*
* Equivalent to calling:
*
* builder .callLogger().type(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder callLogger(Class extends CallLogger> value) {
callLogger().type(value);
return this;
}
/**
* Specifies the call logger for this class.
*
*
* Equivalent to calling:
*
* builder .callLogger().impl(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder callLogger(CallLogger value) {
callLogger().impl(value);
return this;
}
/**
* Instantiates the call logger sub-builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new call logger sub-builder.
*/
protected BeanCreator createCallLogger(BeanStore beanStore, Supplier> resource) {
BeanCreator creator = beanStore.createBean(CallLogger.class).type(BasicCallLogger.class);
// Specify the bean type if its set as a default.
defaultClasses()
.get(CallLogger.class)
.ifPresent(x -> creator.type(x));
beanStore
.getBean(CallLogger.class)
.ifPresent(x -> creator.impl(x));
// Replace with bean from: @RestInject public [static] CallLogger xxx()
beanStore
.createMethodFinder(CallLogger.class)
.find(Builder::isRestBeanMethod)
.run(x -> creator.impl(x));
return creator;
}
//-----------------------------------------------------------------------------------------------------------------
// beanContext
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the bean context sub-builder.
*
*
* The bean context is used to retrieve metadata on Java beans.
*
*
* The default bean context can overridden via any of the following:
*
* Injected via bean store.
*
*
* @return The bean context sub-builder.
*/
public BeanContext.Builder beanContext() {
if (beanContext == null)
beanContext = createBeanContext(beanStore(), resource());
return beanContext;
}
/**
* Instantiates the bean context sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new bean context sub-builder.
*/
protected BeanContext.Builder createBeanContext(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
BeanContext.create()
);
// Replace with builder from bean store.
beanStore
.getBean(BeanContext.Builder.class)
.map(BeanContext.Builder::copy)
.ifPresent(x -> v.set(x));
// Replace with bean from bean store.
beanStore
.getBean(BeanContext.class)
.ifPresent(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// partSerializer
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the part serializer sub-builder.
*
*
* The part serializer is used for serializing HTTP parts such as response headers.
*
*
* The default part serializer is an {@link OpenApiSerializer}.
* It can overridden via any of the following:
*
*
* See Also:
*
* @return The part serializer sub-builder.
*/
public HttpPartSerializer.Creator partSerializer() {
if (partSerializer == null)
partSerializer = createPartSerializer(beanStore(), resource());
return partSerializer;
}
/**
* Specifies the part serializer to use for serializing HTTP parts for this class.
*
*
* Equivalent to calling:
*
* builder .partSerializer().type(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder partSerializer(Class extends HttpPartSerializer> value) {
partSerializer().type(value);
return this;
}
/**
* Specifies the part serializer to use for serializing HTTP parts for this class.
*
*
* Equivalent to calling:
*
* builder .partSerializer().impl(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder partSerializer(HttpPartSerializer value) {
partSerializer().impl(value);
return this;
}
/**
* Instantiates the part serializer sub-builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new part serializer sub-builder.
*/
protected HttpPartSerializer.Creator createPartSerializer(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
HttpPartSerializer
.creator()
.type(OpenApiSerializer.class)
);
// Replace with builder from bean store.
beanStore
.getBean(HttpPartSerializer.Creator.class)
.map(Creator::copy)
.ifPresent(x -> v.set(x));
// Replace with bean from bean store.
beanStore
.getBean(HttpPartSerializer.class)
.ifPresent(x -> v.get().impl(x));
// Replace with this bean.
resourceAs(HttpPartSerializer.class)
.ifPresent(x -> v.get().impl(x));
// Specify the bean type if its set as a default.
defaultClasses()
.get(HttpPartSerializer.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from: @RestInject public [static] HttpPartSerializer xxx()
beanStore
.createMethodFinder(HttpPartSerializer.class)
.addBean(HttpPartSerializer.Creator.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// partParser
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the part parser sub-builder.
*
*
* The part parser is used for parsing HTTP parts such as request headers and query/form/path parameters.
*
*
* The default part parser is an {@link OpenApiParser}.
* It can overridden via any of the following:
*
*
* See Also:
*
* @return The part parser sub-builder.
*/
public HttpPartParser.Creator partParser() {
if (partParser == null)
partParser = createPartParser(beanStore(), resource());
return partParser;
}
/**
* Specifies the part parser to use for parsing HTTP parts for this class.
*
*
* Equivalent to calling:
*
* builder .partParser().type(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder partParser(Class extends HttpPartParser> value) {
partParser().type(value);
return this;
}
/**
* Specifies the part parser to use for parsing HTTP parts for this class.
*
*
* Equivalent to calling:
*
* builder .partParser().impl(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder partParser(HttpPartParser value) {
partParser().impl(value);
return this;
}
/**
* Instantiates the part parser sub-builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new part parser sub-builder.
*/
protected HttpPartParser.Creator createPartParser(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
HttpPartParser
.creator()
.type(OpenApiParser.class)
);
// Replace with builder from bean store.
beanStore
.getBean(HttpPartParser.Creator.class)
.map(HttpPartParser.Creator::copy)
.ifPresent(x -> v.set(x));
// Replace with bean from bean store.
beanStore
.getBean(HttpPartParser.class)
.ifPresent(x -> v.get().impl(x));
// Replace with this bean.
resourceAs(HttpPartParser.class)
.ifPresent(x -> v.get().impl(x));
// Specify the bean type if its set as a default.
defaultClasses()
.get(HttpPartParser.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from: @RestInject public [static] HttpPartParser xxx()
beanStore
.createMethodFinder(HttpPartParser.class)
.addBean(HttpPartParser.Creator.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// jsonSchemaGenerator
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the JSON schema generator sub-builder.
*
*
* The JSON schema generator is used for generating JSON schema in the auto-generated Swagger documentation.
*
*
* The default JSON schema generator is a default {@link JsonSchemaGenerator}.
* It can overridden via any of the following:
*
*
* See Also:
*
* @return The JSON schema generator sub-builder.
*/
public JsonSchemaGenerator.Builder jsonSchemaGenerator() {
if (jsonSchemaGenerator == null)
jsonSchemaGenerator = createJsonSchemaGenerator(beanStore(), resource());
return jsonSchemaGenerator;
}
/**
* Specifies the JSON schema generator for this class.
*
*
* Equivalent to calling:
*
* builder .jsonSchemaGenerator().type(value );
*
*
* See Also:
* Swagger
* {@link #jsonSchemaGenerator()}
*
*
* @param value The new value.
* @return This object.
*/
public Builder jsonSchemaGenerator(Class extends JsonSchemaGenerator> value) {
jsonSchemaGenerator().type(value);
return this;
}
/**
* Specifies the JSON schema generator for this class.
*
*
* Equivalent to calling:
*
* builder .jsonSchemaGenerator().impl(value );
*
{@link #jsonSchemaGenerator()}
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder jsonSchemaGenerator(JsonSchemaGenerator value) {
jsonSchemaGenerator().impl(value);
return this;
}
/**
* Instantiates the JSON schema generator sub-builder.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new JSON schema generator sub-builder.
*/
protected JsonSchemaGenerator.Builder createJsonSchemaGenerator(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
JsonSchemaGenerator.create()
);
// Replace with builder from bean store.
beanStore
.getBean(JsonSchemaGenerator.Builder.class)
.map(JsonSchemaGenerator.Builder::copy)
.ifPresent(x -> v.set(x));
// Replace with bean from bean store.
beanStore
.getBean(JsonSchemaGenerator.class)
.ifPresent(x -> v.get().impl(x));
// Replace with bean from: @RestInject public [static] JsonSchemaGenerator xxx()
beanStore
.createMethodFinder(JsonSchemaGenerator.class)
.addBean(JsonSchemaGenerator.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// staticFiles
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the static files bean creator.
*
*
* Used to retrieve localized files to be served up as static files through the REST API via the following
* predefined methods:
*
* {@link BasicRestObject#getHtdoc(String, Locale)}.
* {@link BasicRestServlet#getHtdoc(String, Locale)}.
*
*
*
* The static file finder can be accessed through the following methods:
*
* {@link RestContext#getStaticFiles()}
* {@link RestRequest#getStaticFiles()}
*
*
*
* The default static files finder implementation class is {@link BasicStaticFiles}. This can be overridden via the following:
*
*
* The {@link Rest#staticFiles() @Rest(staticFiles)} annotation.
*
* Overridden {@link StaticFiles} implementation class name specified in {@link #defaultClasses()}.
*
* Type specified via {@link RestContext.Builder}.{@link #staticFiles() staticFiles()}.{@link org.apache.juneau.rest.staticfile.StaticFiles.Builder#type(Class) type(Class)} .
*
* Bean specified via {@link RestContext.Builder}.{@link #staticFiles() staticFiles()}.{@link org.apache.juneau.rest.staticfile.StaticFiles.Builder#impl(Object) impl(Object)} .
*
*
* Example:
*
* // Create a static file finder that looks for files in the /files working subdirectory, but
* // overrides the find() and resolve methods for special handling of special cases and adds a
* // Foo header to all requests.
* public class MyStaticFiles extends BasicStaticFiles {
*
* public MyStaticFiles() {
* super (
* StaticFiles
* .create ()
* .dir("/files" )
* .headers(BasicStringHeader.of ("Foo" , "bar" ))
* );
* }
* }
*
*
* @Rest (staticFiles=MyStaticFiles.class )
* public class MyResource {...}
*
*
* See Also:
*
* @return The static files bean creator.
*/
public BeanCreator staticFiles() {
if (staticFiles == null)
staticFiles = createStaticFiles(beanStore, resource);
return staticFiles;
}
/**
* Specifies the static files resource finder for this class.
*
*
* Equivalent to calling:
*
* builder .staticFiles().type(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder staticFiles(Class extends StaticFiles> value) {
staticFiles().type(value);
return this;
}
/**
* Specifies the static files resource finder for this class.
*
*
* Equivalent to calling:
*
* builder .staticFiles().impl(value );
*
*
* See Also:
*
* @param value The new value.
* @return This object.
*/
public Builder staticFiles(StaticFiles value) {
staticFiles().impl(value);
return this;
}
/**
* Instantiates the static files bean creator.
*
* See Also:
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new static files sub-builder.
*/
protected BeanCreator createStaticFiles(BeanStore beanStore, Supplier> resource) {
BeanCreator creator = beanStore.createBean(StaticFiles.class).type(BasicStaticFiles.class);
// Specify the bean type if its set as a default.
defaultClasses()
.get(StaticFiles.class)
.ifPresent(x -> creator.type(x));
beanStore
.getBean(StaticFiles.class)
.ifPresent(x -> creator.impl(x));
// Replace with bean from: @RestInject public [static] StaticFiles xxx()
beanStore
.createMethodFinder(StaticFiles.class)
.find(Builder::isRestBeanMethod)
.run(x -> creator.impl(x));
return creator;
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestHeaders
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request headers.
*
* @return The default request headers.
*/
public HeaderList defaultRequestHeaders() {
if (defaultRequestHeaders == null)
defaultRequestHeaders = createDefaultRequestHeaders(beanStore(), resource());
return defaultRequestHeaders;
}
/**
* Default request headers.
*
*
* Specifies default values for request headers if they're not passed in through the request.
*
*
Notes:
*
* Affects values returned by {@link RestRequest#getHeader(String)} when the header is not present on the request.
*
* The most useful reason for this annotation is to provide a default Accept header when one is not
* specified so that a particular default {@link Serializer} is picked.
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (defaultRequestHeaders={"Accept: application/json" , "My-Header=$C{REST/myHeaderValue}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder
* .defaultRequestHeaders(
* Accept.of ("application/json" ),
* BasicHeader.of ("My-Header" , "foo" )
* );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .defaultRequestHeaders(Accept.of ("application/json" ));
* }
*
* // Override at the method level.
* @RestGet (defaultRequestHeaders={"Accept: text/xml" })
* public Object myMethod() {...}
* }
*
*
* See Also:
* {@link Rest#defaultRequestHeaders}
* {@link RestOp#defaultRequestHeaders}
* {@link RestGet#defaultRequestHeaders}
* {@link RestPut#defaultRequestHeaders}
* {@link RestPost#defaultRequestHeaders}
* {@link RestDelete#defaultRequestHeaders}
*
*
* @param values The headers to add.
* @return This object.
*/
@FluentSetter
public Builder defaultRequestHeaders(Header...values) {
defaultRequestHeaders().setDefault(values);
return this;
}
/**
* Specifies a default Accept header value if not specified on a request.
*
* @param value
* The default value of the Accept header.
* Ignored if null or empty.
* @return This object.
*/
@FluentSetter
public Builder defaultAccept(String value) {
if (isNotEmpty(value))
defaultRequestHeaders(accept(value));
return this;
}
/**
* Specifies a default Content-Type header value if not specified on a request.
*
* @param value
* The default value of the Content-Type header.
* Ignored if null or empty.
* @return This object.
*/
@FluentSetter
public Builder defaultContentType(String value) {
if (isNotEmpty(value))
defaultRequestHeaders(contentType(value));
return this;
}
/**
* Instantiates the default request headers sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new default request headers sub-builder.
*/
protected HeaderList createDefaultRequestHeaders(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
HeaderList.create()
);
// Replace with bean from bean store.
beanStore
.getBean(HeaderList.class, "defaultRequestHeaders")
.ifPresent(x -> v.set(x));
// Replace with bean from: @RestInject(name="defaultRequestHeaders") public [static] HeaderList xxx()
beanStore
.createMethodFinder(HeaderList.class)
.addBean(HeaderList.class, v.get())
.find(x -> isRestBeanMethod(x, "defaultRequestHeaders"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// defaultResponseHeaders
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default response headers.
*
* @return The default response headers.
*/
public HeaderList defaultResponseHeaders() {
if (defaultResponseHeaders == null)
defaultResponseHeaders = createDefaultResponseHeaders(beanStore(), resource());
return defaultResponseHeaders;
}
/**
* Default response headers.
*
*
* Specifies default values for response headers if they're not set after the Java REST method is called.
*
*
Notes:
*
* This is equivalent to calling {@link RestResponse#setHeader(String, String)} programmatically in each of
* the Java methods.
*
* The header value will not be set if the header value has already been specified (hence the 'default' in the name).
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (defaultResponseHeaders={"Content-Type: $C{REST/defaultContentType,text/plain}" ,"My-Header: $C{REST/myHeaderValue}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder
* .defaultResponseHeaders(
* ContentType.of ("text/plain" ),
* BasicHeader.ofPair ("My-Header: foo" )
* );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .defaultResponseHeaders(ContentType.of ("text/plain" ));
* }
* }
*
*
* See Also:
* {@link Rest#defaultResponseHeaders}
* {@link RestOp#defaultResponseHeaders}
* {@link RestGet#defaultResponseHeaders}
* {@link RestPut#defaultResponseHeaders}
* {@link RestPost#defaultResponseHeaders}
* {@link RestDelete#defaultResponseHeaders}
*
*
* @param values The headers to add.
* @return This object.
*/
@FluentSetter
public Builder defaultResponseHeaders(Header...values) {
defaultResponseHeaders().setDefault(values);
return this;
}
/**
* Instantiates the default response headers sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new default response headers sub-builder.
*/
protected HeaderList createDefaultResponseHeaders(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
HeaderList.create()
);
// Replace with bean from bean store.
beanStore
.getBean(HeaderList.class, "defaultResponseHeaders")
.ifPresent(x -> v.set(x));
// Replace with bean from: @RestInject(name="defaultResponseHeaders") public [static] HeaderList xxx()
beanStore
.createMethodFinder(HeaderList.class)
.addBean(HeaderList.class, v.get())
.find(x -> isRestBeanMethod(x, "defaultResponseHeaders"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestAttributes
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request attributes sub-builder.
*
* @return The default request attributes sub-builder.
*/
public NamedAttributeMap defaultRequestAttributes() {
if (defaultRequestAttributes == null)
defaultRequestAttributes = createDefaultRequestAttributes(beanStore(), resource());
return defaultRequestAttributes;
}
/**
* Default request attributes.
*
*
* Specifies default values for request attributes if they're not already set on the request.
*
* Affects values returned by the following methods:
*
* {@link RestRequest#getAttribute(String)}.
* {@link RestRequest#getAttributes()}.
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (defaultRequestAttributes={"Foo=bar" , "Baz: $C{REST/myAttributeValue}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder
* .defaultRequestAttributes(
* BasicNamedAttribute.of ("Foo" , "bar" ),
* BasicNamedAttribute.of ("Baz" , true )
* );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .defaultRequestAttribute("Foo" , "bar" );
* }
*
* // Override at the method level.
* @RestGet (defaultRequestAttributes={"Foo: bar" })
* public Object myMethod() {...}
* }
*
*
* Notes:
* Use {@link BasicNamedAttribute#of(String, Supplier)} to provide a dynamically changeable attribute value.
*
*
* @param values The attributes.
* @return This object.
*/
@FluentSetter
public Builder defaultRequestAttributes(NamedAttribute...values) {
defaultRequestAttributes().add(values);
return this;
}
/**
* Instantiates the default request attributes sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new default request attributes sub-builder.
*/
protected NamedAttributeMap createDefaultRequestAttributes(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
NamedAttributeMap.create()
);
beanStore
.getBean(NamedAttributeMap.class, "defaultRequestAttributes")
.ifPresent(x -> v.set(x));
// Replace with bean from: @RestInject(name="defaultRequestAttributes") public [static] NamedAttributeMap xxx()
beanStore
.createMethodFinder(NamedAttributeMap.class)
.addBean(NamedAttributeMap.class, v.get())
.find(x -> isRestBeanMethod(x, "defaultRequestAttributes"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// restOpArgs
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the REST operation args sub-builder.
*
* @return The REST operation args sub-builder.
*/
public RestOpArgList.Builder restOpArgs() {
if (restOpArgs == null)
restOpArgs = createRestOpArgs(beanStore(), resource());
return restOpArgs;
}
/**
* Adds one or more REST operation args to this class.
*
*
* Equivalent to calling:
*
* builder .restOpArgs().add(value );
*
*
* @param value The new value.
* @return This object.
*/
@SafeVarargs
@FluentSetter
public final Builder restOpArgs(Class extends RestOpArg>...value) {
restOpArgs().add(value);
return this;
}
/**
* Instantiates the REST operation args sub-builder.
*
*
* Instantiates based on the following logic:
*
* Looks for REST op args set via any of the following:
*
* {@link RestContext.Builder#restOpArgs(Class...)}/{@link RestContext.Builder#restOpArgs(Class...)}
* {@link Rest#restOpArgs()}.
*
* Looks for a static or non-static createRestParams() method that returns {@link Class}[] .
* Resolves it via the bean store registered in this context.
* Instantiates a default set of parameters.
*
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new REST operation args sub-builder.
*/
protected RestOpArgList.Builder createRestOpArgs(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
RestOpArgList
.create(beanStore)
.add(
AttributeArg.class,
ContentArg.class,
FormDataArg.class,
HasFormDataArg.class,
HasQueryArg.class,
HeaderArg.class,
HttpServletRequestArgs.class,
HttpServletResponseArgs.class,
HttpSessionArgs.class,
InputStreamParserArg.class,
MethodArg.class,
ParserArg.class,
PathArg.class,
QueryArg.class,
ReaderParserArg.class,
RequestBeanArg.class,
ResponseBeanArg.class,
ResponseHeaderArg.class,
ResponseCodeArg.class,
RestContextArgs.class,
RestSessionArgs.class,
RestOpContextArgs.class,
RestOpSessionArgs.class,
RestRequestArgs.class,
RestResponseArgs.class,
DefaultArg.class
)
);
// Replace with bean from bean store.
beanStore
.getBean(RestOpArgList.class)
.ifPresent(x -> v.get().impl(x));
// Replace with bean from: @RestInject public [static] RestOpArgList xxx()
beanStore
.createMethodFinder(RestOpArgList.class)
.addBean(RestOpArgList.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// debugEnablement
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the debug enablement bean creator.
*
*
* Enables the following:
*
*
* HTTP request/response bodies are cached in memory for logging purposes.
*
* Request/response messages are automatically logged always or per request.
*
*
* @return The debug enablement sub-builder.
*/
public BeanCreator debugEnablement() {
if (debugEnablement == null)
debugEnablement = createDebugEnablement(beanStore, resource);
return debugEnablement;
}
/**
* Specifies the debug enablement class to use for this REST context.
*
* @param value The new value for this setting.
* @return This object.
*/
public Builder debugEnablement(Class extends DebugEnablement> value) {
debugEnablement().type(value);
return this;
}
/**
* Specifies the debug enablement class to use for this REST context.
*
* @param value The new value for this setting.
* @return This object.
*/
public Builder debugEnablement(DebugEnablement value) {
debugEnablement().impl(value);
return this;
}
/**
* Sets the debug default value.
*
*
* The default debug value is the enablement value if not otherwise overridden at the class or method level.
*
* @param value The debug default value.
* @return This object.
*/
@FluentSetter
public Builder debugDefault(Enablement value) {
defaultSettings().set("RestContext.debugDefault", value);
return this;
}
/**
* Instantiates the debug enablement bean creator.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new debug enablement bean creator.
*/
protected BeanCreator createDebugEnablement(BeanStore beanStore, Supplier> resource) {
BeanCreator creator = beanStore.createBean(DebugEnablement.class).type(BasicDebugEnablement.class);
// Specify the bean type if its set as a default.
defaultClasses()
.get(DebugEnablement.class)
.ifPresent(x -> creator.type(x));
beanStore
.getBean(DebugEnablement.class)
.ifPresent(x -> creator.impl(x));
// Replace with bean from: @RestInject public [static] DebugEnablement xxx()
beanStore
.createMethodFinder(DebugEnablement.class)
.find(Builder::isRestBeanMethod)
.run(x -> creator.impl(x));
return creator;
}
//-----------------------------------------------------------------------------------------------------------------
// HookEvent.START_CALL methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the start call method list.
*
* @return The start call method list.
*/
public MethodList startCallMethods() {
if (startCallMethods == null)
startCallMethods = createStartCallMethods(beanStore(), resource());
return startCallMethods;
}
/**
* Instantiates the start call method list.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new start call method list.
*/
protected MethodList createStartCallMethods(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
getAnnotatedMethods(resource, RestStartCall.class, x -> true)
);
// Replace with bean from: @RestInject(name="startCallMethods") public [static] MethodList xxx()
beanStore
.createMethodFinder(MethodList.class)
.addBean(MethodList.class, v.get())
.find(x -> isRestBeanMethod(x, "startCallMethods"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// HookEvent.END_CALL methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the end call method list.
*
* @return The end call method list.
*/
public MethodList endCallMethods() {
if (endCallMethods == null)
endCallMethods = createEndCallMethods(beanStore(), resource());
return endCallMethods;
}
/**
* Instantiates the end call method list.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new end call method list.
*/
protected MethodList createEndCallMethods(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
getAnnotatedMethods(resource, RestEndCall.class, x -> true)
);
// Replace with bean from: @RestInject(name="endCallMethods") public [static] MethodList xxx()
beanStore
.createMethodFinder(MethodList.class)
.addBean(MethodList.class, v.get())
.find(x -> isRestBeanMethod(x, "endCallMethods"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// HookEvent.POST_INIT methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the post-init method list.
*
* @return The post-init method list.
*/
public MethodList postInitMethods() {
if (postInitMethods == null)
postInitMethods = createPostInitMethods(beanStore(), resource());
return postInitMethods;
}
/**
* Instantiates the post-init method list.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new post-init method list.
*/
protected MethodList createPostInitMethods(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
getAnnotatedMethods(resource, RestPostInit.class, x -> ! x.childFirst())
);
// Replace with bean from: @RestInject(name="postInitMethods") public [static] MethodList xxx()
beanStore
.createMethodFinder(MethodList.class)
.addBean(MethodList.class, v.get())
.find(x -> isRestBeanMethod(x, "postInitMethods"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// HookEvent.POST_INIT_CHILD_FIRST methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the post-init-child-first method list.
*
* @return The post-init-child-first method list.
*/
public MethodList postInitChildFirstMethods() {
if (postInitChildFirstMethods == null)
postInitChildFirstMethods = createPostInitChildFirstMethods(beanStore(), resource());
return postInitChildFirstMethods;
}
/**
* Instantiates the post-init-child-first method list.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new post-init-child-first method list.
*/
protected MethodList createPostInitChildFirstMethods(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
getAnnotatedMethods(resource, RestPostInit.class, RestPostInit::childFirst)
);
// Replace with bean from: @RestInject(name="postInitChildFirstMethods") public [static] MethodList xxx()
beanStore
.createMethodFinder(MethodList.class)
.addBean(MethodList.class, v.get())
.find(x -> isRestBeanMethod(x, "postInitChildFirstMethods"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// HookEvent.DESTROY methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the destroy method list.
*
* @return The destroy method list.
*/
public MethodList destroyMethods() {
if (destroyMethods == null)
destroyMethods = createDestroyMethods(beanStore(), resource());
return destroyMethods;
}
/**
* Instantiates the destroy method list.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new destroy method list.
*/
protected MethodList createDestroyMethods(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
getAnnotatedMethods(resource, RestDestroy.class, x -> true)
);
// Replace with bean from: @RestInject(name="destroyMethods") public [static] MethodList xxx()
beanStore
.createMethodFinder(MethodList.class)
.addBean(MethodList.class, v.get())
.find(x -> isRestBeanMethod(x, "destroyMethods"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// HookEvent.PRE_CALL methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the pre-call method list.
*
*
* The list of methods that gets called immediately before the @RestOp annotated method gets called.
*
* @return The pre-call method list.
*/
public MethodList preCallMethods() {
if (preCallMethods == null)
preCallMethods = createPreCallMethods(beanStore(), resource());
return preCallMethods;
}
/**
* Instantiates the pre-call method list.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new pre-call method list.
*/
protected MethodList createPreCallMethods(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
getAnnotatedMethods(resource, RestPreCall.class, x -> true)
);
// Replace with bean from: @RestInject(name="preCallMethods") public [static] MethodList xxx()
beanStore
.createMethodFinder(MethodList.class)
.addBean(MethodList.class, v.get())
.find(x -> isRestBeanMethod(x, "preCallMethods"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// HookEvent.POST_CALL methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the post-call method list.
*
*
* The list of methods that gets called immediately after the @RestOp annotated method gets called..
*
* @return The list of methods that gets called immediately after the @RestOp annotated method gets called..
*/
public MethodList postCallMethods() {
if (postCallMethods == null)
postCallMethods = createPostCallMethods(beanStore(), resource());
return postCallMethods;
}
/**
* Instantiates the post-call method list.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new post-call method list.
*/
protected MethodList createPostCallMethods(BeanStore beanStore, Supplier> resource) {
// Default value.
Value v = Value.of(
getAnnotatedMethods(resource, RestPostCall.class, x -> true)
);
// Replace with bean from: @RestInject(name="postCallMethods") public [static] MethodList xxx()
beanStore
.createMethodFinder(MethodList.class)
.addBean(MethodList.class, v.get())
.find(x -> isRestBeanMethod(x, "postCallMethods"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// restOperations
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the REST operations list.
*
* @param restContext The rest context.
* @return The REST operations list.
* @throws ServletException If a problem occurred instantiating one of the child rest contexts.
*/
public RestOperations.Builder restOperations(RestContext restContext) throws ServletException {
if (restOperations == null)
restOperations = createRestOperations(beanStore(), resource(), restContext);
return restOperations;
}
/**
* Instantiates the REST operations list.
*
*
* The set of {@link RestOpContext} objects that represent the methods on this resource.
*
* @param restContext The rest context.
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new REST operations list.
* @throws ServletException If a problem occurred instantiating one of the child rest contexts.
*/
protected RestOperations.Builder createRestOperations(BeanStore beanStore, Supplier> resource, RestContext restContext) throws ServletException {
// Default value.
Value v = Value.of(
RestOperations.create(beanStore)
);
ClassInfo rci = ClassInfo.of(resource.get());
Map initMap = map();
ClassInfo.ofProxy(resource.get()).forEachAllMethodParentFirst(
y -> y.hasAnnotation(RestInit.class) && y.hasArg(RestOpContext.Builder.class),
y -> {
String sig = y.getSignature();
if (! initMap.containsKey(sig))
initMap.put(sig, y.accessible());
}
);
for (MethodInfo mi : rci.getPublicMethods()) {
AnnotationList al = mi.getAnnotationList(REST_OP_GROUP);
// Also include methods on @Rest-annotated interfaces.
if (al.size() == 0) {
Predicate isRestAnnotatedInterface = x -> x.getDeclaringClass().isInterface() && x.getDeclaringClass().getAnnotation(Rest.class) != null;
mi.forEachMatching(isRestAnnotatedInterface, x -> al.add(AnnotationInfo.of(x, RestOpAnnotation.DEFAULT)));
}
if (al.size() > 0) {
try {
if (mi.isNotPublic())
throw servletException("@RestOp method {0}.{1} must be defined as public.", rci.inner().getName(), mi.getSimpleName());
RestOpContext.Builder rocb = RestOpContext
.create(mi.inner(), restContext)
.beanStore(beanStore)
.type(opContextClass);
beanStore = BeanStore.of(beanStore, resource.get()).addBean(RestOpContext.Builder.class, rocb);
for (MethodInfo m : initMap.values()) {
if (! beanStore.hasAllParams(m)) {
throw servletException("Could not call @RestInit method {0}.{1}. Could not find prerequisites: {2}.", m.getDeclaringClass().getSimpleName(), m.getSignature(), beanStore.getMissingParams(m));
}
try {
m.invoke(resource.get(), beanStore.getParams(m));
} catch (Exception e) {
throw servletException(e, "Exception thrown from @RestInit method {0}.{1}.", m.getDeclaringClass().getSimpleName(), m.getSignature());
}
}
RestOpContext roc = rocb.build();
String httpMethod = roc.getHttpMethod();
// RRPC is a special case where a method returns an interface that we
// can perform REST calls against.
// We override the CallMethod.invoke() method to insert our logic.
if ("RRPC".equals(httpMethod)) {
RestOpContext roc2 = RestOpContext
.create(mi.inner(), restContext)
.dotAll()
.beanStore(restContext.getRootBeanStore())
.type(RrpcRestOpContext.class)
.build();
v.get()
.add("GET", roc2)
.add("POST", roc2);
} else {
v.get().add(roc);
}
} catch (Throwable e) {
throw servletException(e, "Problem occurred trying to initialize methods on class {0}", rci.inner().getName());
}
}
}
// Replace with bean from: @RestInject public [static] RestOperations xxx()
beanStore
.createMethodFinder(RestOperations.class)
.addBean(RestOperations.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// restChildren
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the REST children list.
*
* @param restContext The rest context.
* @return The REST children list.
* @throws Exception If a problem occurred instantiating one of the child rest contexts.
*/
public RestChildren.Builder restChildren(RestContext restContext) throws Exception {
if (restChildren == null)
restChildren = createRestChildren(beanStore(), resource(), restContext);
return restChildren;
}
/**
* Instantiates the REST children list.
*
* @param restContext The rest context.
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new REST children list.
* @throws Exception If a problem occurred instantiating one of the child rest contexts.
*/
protected RestChildren.Builder createRestChildren(BeanStore beanStore, Supplier> resource, RestContext restContext) throws Exception {
// Default value.
Value v = Value.of(
RestChildren
.create(beanStore)
.type(childrenClass)
);
// Initialize our child resources.
for (Object o : children) {
String path = null;
Supplier> so;
if (o instanceof RestChild) {
RestChild rc = (RestChild)o;
path = rc.path;
Object o2 = rc.resource;
so = ()->o2;
}
Builder cb = null;
if (o instanceof Class) {
Class> oc = (Class>)o;
// Don't allow specifying yourself as a child. Causes an infinite loop.
if (oc == resourceClass)
continue;
cb = RestContext.create(oc, restContext, inner);
if (beanStore.getBean(oc).isPresent()) {
so = ()->beanStore.getBean(oc).get(); // If we resolved via injection, always get it this way.
} else {
Object o2 = beanStore.createBean(oc).builder(RestContext.Builder.class, cb).run();
so = ()->o2;
}
} else {
cb = RestContext.create(o.getClass(), restContext, inner);
so = ()->o;
}
if (path != null)
cb.path(path);
RestContext cc = cb.init(so).build();
MethodInfo mi = ClassInfo.of(so.get()).getMethod(
x -> x.hasName("setContext")
&& x.hasParamTypes(RestContext.class)
);
if (mi != null)
mi.accessible().invoke(so.get(), cc);
v.get().add(cc);
}
// Replace with bean from: @RestInject public [static] RestChildren xxx()
beanStore
.createMethodFinder(RestChildren.class)
.addBean(RestChildren.Builder.class, v.get())
.find(Builder::isRestBeanMethod)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// swaggerProvider
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the swagger provider sub-builder.
*
* @return The swagger provider sub-builder.
*/
public BeanCreator swaggerProvider() {
if (swaggerProvider == null)
swaggerProvider = createSwaggerProvider(beanStore, resource);
return swaggerProvider;
}
/**
* Specifies the swagger provider for this class.
*
*
* Equivalent to calling:
*
* builder .swaggerProvider().type(value );
*
*
* @param value The new value.
* @return This object.
*/
public Builder swaggerProvider(Class extends SwaggerProvider> value) {
swaggerProvider().type(value);
return this;
}
/**
* Specifies the swagger provider for this class.
*
*
* Equivalent to calling:
*
* builder .swaggerProvider().impl(value );
*
*
* @param value The new value.
* @return This object.
*/
public Builder swaggerProvider(SwaggerProvider value) {
swaggerProvider().impl(value);
return this;
}
/**
* Instantiates the swagger provider sub-builder.
*
*
* Instantiates based on the following logic:
*
* Returns the resource class itself is an instance of {@link SwaggerProvider}.
* Looks for swagger provider set via any of the following:
*
* {@link RestContext.Builder#swaggerProvider(Class)}/{@link RestContext.Builder#swaggerProvider(SwaggerProvider)}
* {@link Rest#swaggerProvider()}.
*
* Looks for a static or non-static createSwaggerProvider() method that returns {@link SwaggerProvider} on the
* resource class with any of the following arguments:
*
* Resolves it via the bean store registered in this context.
* Instantiates a default {@link BasicSwaggerProvider}.
*
*
* See Also:
* {@link RestContext.Builder#swaggerProvider(Class)}
* {@link RestContext.Builder#swaggerProvider(SwaggerProvider)}
*
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new swagger provider sub-builder.
*/
protected BeanCreator createSwaggerProvider(BeanStore beanStore, Supplier> resource) {
BeanCreator creator = beanStore.createBean(SwaggerProvider.class).type(BasicSwaggerProvider.class);
// Specify the bean type if its set as a default.
defaultClasses()
.get(SwaggerProvider.class)
.ifPresent(x -> creator.type(x));
beanStore
.getBean(SwaggerProvider.class)
.ifPresent(x -> creator.impl(x));
// Replace with bean from: @RestInject public [static] SwaggerProvider xxx()
beanStore
.createMethodFinder(SwaggerProvider.class)
.find(Builder::isRestBeanMethod)
.run(x -> creator.impl(x));
return creator;
}
//-----------------------------------------------------------------------------------------------------------------
// Miscellaneous settings
//-----------------------------------------------------------------------------------------------------------------
/**
* Allowed header URL parameters.
*
*
* When specified, allows headers such as "Accept" and "Content-Type" to be passed in as URL query
* parameters.
*
* For example:
*
* ?Accept=text/json&Content-Type=text/json
*
*
* Notes:
*
* Useful for debugging REST interface using only a browser so that you can quickly simulate header values
* in the URL bar.
*
* Header names are case-insensitive.
*
* Use "*" to allow any headers to be specified as URL parameters.
*
* Use "NONE" (case insensitive) to suppress inheriting a value from a parent class.
*
* See Also:
* {@link Rest#allowedHeaderParams}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.allowedHeaderParams"
* Environment variable "RESTCONTEXT_ALLOWEDHEADERPARAMS"
* "Accept,Content-Type"
*
* @return This object.
*/
@FluentSetter
public Builder allowedHeaderParams(String value) {
allowedHeaderParams = value;
return this;
}
/**
* Allowed method headers.
*
*
* A comma-delimited list of HTTP method names that are allowed to be passed as values in an X-Method HTTP header
* to override the real HTTP method name.
*
*
* Allows you to override the actual HTTP method with a simulated method.
* For example, if an HTTP Client API doesn't support PATCH but does support POST (because
* PATCH is not part of the original HTTP spec), you can add a X-Method: PATCH header on a normal
* HTTP POST /foo request call which will make the HTTP call look like a PATCH request in any of the REST APIs.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (allowedMethodHeaders="PATCH" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .allowedMethodHeaders("PATCH" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .allowedMethodHeaders("PATCH" );
* }
* }
*
*
* Notes:
*
* Method names are case-insensitive.
*
* Use "*" to represent all methods.
*
* Use "NONE" (case insensitive) to suppress inheriting a value from a parent class.
*
*
* See Also:
* {@link Rest#allowedMethodHeaders}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.allowedMethodHeaders"
* Environment variable "RESTCONTEXT_ALLOWEDMETHODHEADERS"
* ""
*
* @return This object.
*/
@FluentSetter
public Builder allowedMethodHeaders(String value) {
allowedMethodHeaders = value;
return this;
}
/**
* Allowed method parameters.
*
*
* When specified, the HTTP method can be overridden by passing in a "method" URL parameter on a regular
* GET request.
*
* For example:
*
* ?method=OPTIONS
*
*
*
* Useful in cases where you want to simulate a non-GET request in a browser by simply adding a parameter.
* Also useful if you want to construct hyperlinks to non-GET REST endpoints such as links to OPTIONS
* pages.
*
*
* Note that per the HTTP specification , special care should
* be taken when allowing non-safe (POST , PUT , DELETE ) methods to be invoked through GET requests.
*
*
Example:
*
* // Option #1 - Defined via annotation.
* @Rest (allowedMethodParams="HEAD,OPTIONS,PUT" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .allowedMethodParams("HEAD,OPTIONS,PUT" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder) throws Exception {
* builder .allowedMethodParams("HEAD,OPTIONS,PUT" );
* }
* }
*
*
* Notes:
*
* Format is a comma-delimited list of HTTP method names that can be passed in as a method parameter.
*
* 'method' parameter name is case-insensitive.
*
* Use "*" to represent all methods.
*
* Use "NONE" (case insensitive) to suppress inheriting a value from a parent class.
*
*
* See Also:
* {@link Rest#allowedMethodParams}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.allowedMethodParams"
* Environment variable "RESTCONTEXT_ALLOWEDMETHODPARAMS"
* "HEAD,OPTIONS"
*
* @return This object.
*/
@FluentSetter
public Builder allowedMethodParams(String value) {
allowedMethodParams = value;
return this;
}
/**
* Client version header.
*
*
* Specifies the name of the header used to denote the client version on HTTP requests.
*
*
* The client version is used to support backwards compatibility for breaking REST interface changes.
* Used in conjunction with {@link RestOp#clientVersion() @RestOp(clientVersion)} annotation.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (clientVersionHeader="$C{REST/clientVersionHeader,Client-Version}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .clientVersionHeader("Client-Version" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .clientVersionHeader("Client-Version" );
* }
* }
*
*
*
* // Call this method if Client-Version is at least 2.0.
* // Note that this also matches 2.0.1.
* @RestGet (path="/foobar" , clientVersion="2.0" )
* public Object method1() {
* ...
* }
*
* // Call this method if Client-Version is at least 1.1, but less than 2.0.
* @RestGet (path="/foobar" , clientVersion="[1.1,2.0)" )
* public Object method2() {
* ...
* }
*
* // Call this method if Client-Version is less than 1.1.
* @RestGet (path="/foobar" , clientVersion="[0,1.1)" )
* public Object method3() {
* ...
* }
*
*
* See Also:
* {@link Rest#clientVersionHeader}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.clientVersionHeader"
* Environment variable "RESTCONTEXT_CLIENTVERSIONHEADER"
* "Client-Version"
*
* @return This object.
*/
@FluentSetter
public Builder clientVersionHeader(String value) {
clientVersionHeader = value;
return this;
}
/**
* Default character encoding.
*
*
* The default character encoding for the request and response if not specified on the request.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (defaultCharset="$C{REST/defaultCharset,US-ASCII}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .defaultCharset("US-ASCII" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .defaultCharset("US-ASCII" );
* }
*
* // Override at the method level.
* @RestGet (defaultCharset="UTF-16" )
* public Object myMethod() {...}
* }
*
*
* See Also:
* {@link Rest#defaultCharset}
* {@link RestOp#defaultCharset}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.defaultCharset"
* Environment variable "RESTCONTEXT_defaultCharset"
* "utf-8"
*
* @return This object.
*/
@FluentSetter
public Builder defaultCharset(Charset value) {
defaultCharset = value;
return this;
}
/**
* Disable content URL parameter.
*
*
* When enabled, the HTTP content content on PUT and POST requests can be passed in as text using the "content"
* URL parameter.
*
* For example:
*
* ?content=(name='John%20Smith',age=45)
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (disableContentParam="$C{REST/disableContentParam,true}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .disableContentParam();
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .disableContentParam();
* }
* }
*
*
* Notes:
*
* 'content' parameter name is case-insensitive.
*
* Useful for debugging PUT and POST methods using only a browser.
*
*
* @return This object.
*/
@FluentSetter
public Builder disableContentParam() {
return disableContentParam(true);
}
/**
* Disable content URL parameter.
*
*
* Same as {@link #disableContentParam()} but allows you to set it as a boolean value.
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder disableContentParam(boolean value) {
disableContentParam = value;
return this;
}
/**
* The maximum allowed input size (in bytes) on HTTP requests.
*
*
* 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.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (maxInput="$C{REST/maxInput,10M}" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .maxInput("10M" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .maxInput("10M" );
* }
*
* // Override at the method level.
* @RestPost (maxInput="10M" )
* public Object myMethod() {...}
* }
*
*
* Notes:
*
* String value that gets resolved to a long .
*
* Can be suffixed with any of the following representing kilobytes, megabytes, and gigabytes:
* 'K' , 'M' , 'G' .
*
* A value of "-1" can be used to represent no limit.
*
*
* See Also:
* {@link Rest#maxInput}
* {@link RestOp#maxInput}
* {@link RestOpContext.Builder#maxInput(String)}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.maxInput"
* Environment variable "RESTCONTEXT_MAXINPUT"
* "100M"
*
* The default is "100M" .
* @return This object.
*/
@FluentSetter
public Builder maxInput(String value) {
maxInput = StringUtils.parseLongWithSuffix(value);
return this;
}
/**
* RestContext configuration property: Render response stack traces in responses.
*
*
* Render stack traces in HTTP response bodies when errors occur.
*
* @param value
* The new value for this setting.
* The default is false .
* @return This object.
*/
@FluentSetter
public Builder renderResponseStackTraces(boolean value) {
renderResponseStackTraces = value;
return this;
}
/**
* RestContext configuration property: Render response stack traces in responses.
*
*
* Shortcut for calling renderResponseStackTraces(true )
.
*
* @return This object.
*/
@FluentSetter
public Builder renderResponseStackTraces() {
renderResponseStackTraces = true;
return this;
}
/**
* Resource authority path.
*
*
* Overrides the authority path value for this resource and any child resources.
*
*
* This setting is useful if you want to resolve relative URIs to absolute paths and want to explicitly specify the hostname/port.
*
*
* Affects the following methods:
*
* {@link RestRequest#getAuthorityPath()}
*
*
*
* If you do not specify the authority, it is automatically calculated via the following:
*
*
* String scheme = request .getScheme();
* int port = request .getServerPort();
* StringBuilder sb = new StringBuilder(request .getScheme()).append("://" ).append(request .getServerName());
* if (! (port == 80 && "http" .equals(scheme ) || port == 443 && "https" .equals(scheme )))
* sb .append(':' ).append(port );
* authorityPath = sb .toString();
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (
* path="/servlet" ,
* uriAuthority="$C{REST/authorityPathOverride,http://localhost:10000}"
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .uriAuthority("http://localhost:10000" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .uriAuthority("http://localhost:10000" );
* }
* }
*
*
* See Also:
* {@link Rest#uriAuthority}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.uriAuthority"
* Environment variable "RESTCONTEXT_URIAUTHORITY"
* null
*
* @return This object.
*/
@FluentSetter
public Builder uriAuthority(String value) {
uriAuthority = value;
return this;
}
/**
* Resource context path.
*
*
* Overrides the context path value for this resource and any child resources.
*
*
* This setting is useful if you want to use "context:/child/path" URLs in child resource POJOs but
* the context path is not actually specified on the servlet container.
*
*
* Affects the following methods:
*
* {@link RestRequest#getContextPath()} - Returns the overridden context path for the resource.
* {@link RestRequest#getServletPath()} - Includes the overridden context path for the resource.
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (
* path="/servlet" ,
* uriContext="$C{REST/contextPathOverride,/foo}"
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .uriContext("/foo" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .uriContext("/foo" );
* }
* }
*
*
* See Also:
* {@link Rest#uriContext}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.uriContext"
* Environment variable "RESTCONTEXT_URICONTEXT"
* null
*
* @return This object.
*/
@FluentSetter
public Builder uriContext(String value) {
uriContext = value;
return this;
}
/**
* URI resolution relativity.
*
*
* Specifies how relative URIs should be interpreted by serializers.
*
*
* See {@link UriResolution} for possible values.
*
*
* Affects the following methods:
*
* {@link RestRequest#getUriResolver()}
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (
* path="/servlet" ,
* uriRelativity="$C{REST/uriRelativity,PATH_INFO}"
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .uriRelativity(PATH_INFO );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .uriRelativity(PATH_INFO );
* }
* }
*
*
* See Also:
* {@link Rest#uriRelativity}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.uriRelativity"
* Environment variable "RESTCONTEXT_URIRELATIVITY"
* {@link UriRelativity#RESOURCE}
*
* @return This object.
*/
@FluentSetter
public Builder uriRelativity(UriRelativity value) {
uriRelativity = value;
return this;
}
/**
* URI resolution.
*
*
* Specifies how relative URIs should be interpreted by serializers.
*
*
* See {@link UriResolution} for possible values.
*
*
* Affects the following methods:
*
* {@link RestRequest#getUriResolver()}
*
*
* Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (
* path="/servlet" ,
* uriResolution="$C{REST/uriResolution,ABSOLUTE}"
* )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .uriResolution(ABSOLUTE );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .uriResolution(ABSOLUTE );
* }
* }
*
*
* See Also:
* {@link Rest#uriResolution}
*
*
* @param value
* The new value for this setting.
* The default is the first value found:
*
* System property "RestContext.uriResolution"
* Environment variable "RESTCONTEXT_URIRESOLUTION"
* {@link UriResolution#ROOT_RELATIVE}
*
* @return This object.
*/
@FluentSetter
public Builder uriResolution(UriResolution value) {
uriResolution = value;
return this;
}
//----------------------------------------------------------------------------------------------------
// Methods that give access to the config file, var resolver, and properties.
//----------------------------------------------------------------------------------------------------
/**
* Returns the serializer group builder containing the serializers for marshalling POJOs into response bodies.
*
*
* Serializer are used to convert POJOs to HTTP response bodies.
* Any of the Juneau framework serializers can be used in this setting.
* The serializer selected is based on the request Accept header matched against the values returned by the following method
* using a best-match algorithm:
*
* {@link Serializer#getMediaTypeRanges()}
*
*
*
* The builder is initialized with serializers defined via the {@link Rest#serializers()} annotation. That annotation is applied
* from parent-to-child order with child entries given priority over parent entries.
*
*
See Also:
*
* @return The serializer group builder for this context builder.
*/
public SerializerSet.Builder getSerializers() {
return serializers;
}
/**
* Returns the parser group builder containing the parsers for converting HTTP request bodies into POJOs.
*
*
* Parsers are used to convert the content of HTTP requests into POJOs.
* Any of the Juneau framework parsers can be used in this setting.
* The parser selected is based on the request Content-Type header matched against the values returned by the following method
* using a best-match algorithm:
*
* {@link Parser#getMediaTypes()}
*
*
*
* The builder is initialized with parsers defined via the {@link Rest#parsers()} annotation. That annotation is applied
* from parent-to-child order with child entries given priority over parent entries.
*
*
See Also:
*
* @return The parser group builder for this context builder.
*/
public ParserSet.Builder getParsers() {
return parsers;
}
/**
* Returns the encoder group builder containing the encoders for compressing/decompressing input and output streams.
*
*
* These can be used to enable various kinds of compression (e.g. "gzip" ) on requests and responses.
*
*
* The builder is initialized with encoders defined via the {@link Rest#encoders()} annotation. That annotation is applied
* from parent-to-child order with child entries given priority over parent entries.
*
*
See Also:
*
* @return The encoder group builder for this context builder.
*/
public EncoderSet.Builder getEncoders() {
return encoders;
}
//----------------------------------------------------------------------------------------------------
// Properties
//----------------------------------------------------------------------------------------------------
/**
* Child REST resources.
*
*
* Defines children of this resource.
*
*
* A REST child resource is simply another servlet or object that is initialized as part of the ascendant resource and has a
* servlet path directly under the ascendant resource object path.
* The main advantage to defining servlets as REST children is that you do not need to define them in the
* web.xml file of the web application.
* This can cut down on the number of entries that show up in the web.xml file if you are defining
* large numbers of servlets.
*
*
* Child resources must specify a value for {@link Rest#path() @Rest(path)} that identifies the subpath of the child resource
* relative to the ascendant path UNLESS you use the {@link RestContext.Builder#child(String, Object)} method to register it.
*
*
* Child resources can be nested arbitrarily deep using this technique (i.e. children can also have children).
*
*
* Servlet initialization:
*
*
* A child resource will be initialized immediately after the ascendant servlet/resource is initialized.
* The child resource receives the same servlet config as the ascendant servlet/resource.
* This allows configuration information such as servlet initialization parameters to filter to child
* resources.
*
*
* Runtime behavior:
*
*
* As a rule, methods defined on the HttpServletRequest object will behave as if the child
* servlet were deployed as a top-level resource under the child's servlet path.
* For example, the getServletPath() and getPathInfo() methods on the
* HttpServletRequest object will behave as if the child resource were deployed using the
* child's servlet path.
* Therefore, the runtime behavior should be equivalent to deploying the child servlet in the
* web.xml file of the web application.
*
*
*
*
* Example:
*
* // Our child resource.
* @Rest (path="/child" )
* public class MyChildResource {...}
*
* // Option #1 - Registered via annotation.
* @Rest (children={MyChildResource.class })
* public class MyResource {
*
* // Option #2 - Registered via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .children(MyChildResource.class );
*
* // Use a pre-instantiated object instead.
* builder .child("/child" , new MyChildResource());
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .children(MyChildResource.class );
* }
* }
*
*
* Notes:
*
* When defined as classes, instances are resolved using the registered bean store which
* by default is {@link BeanStore} which requires the class have one of the following
* constructors:
*
* public T(RestContext.Builder)
* public T()
*
*
*
* See Also:
* {@link Rest#children()}
*
*
* @param values
* The values to add to this setting.
* Objects can be any of the specified types:
*
* A class that has a constructor described above.
* An instantiated resource object (such as a servlet object instantiated by a servlet container).
* An instance of {@link RestChild} containing an instantiated resource object and a subpath.
*
* @return This object.
*/
@FluentSetter
public Builder children(Object...values) {
addAll(children, values);
return this;
}
/**
* Add a child REST resource.
*
*
* Shortcut for adding a single child to this resource.
*
*
* This can be used for resources that don't have a {@link Rest#path() @Rest(path)} annotation.
*
* @param path The child path relative to the parent resource URI.
* @param child The child to add to this resource.
* @return This object.
*/
@FluentSetter
public Builder child(String path, Object child) {
children.add(new RestChild(path, child));
return this;
}
/**
* RestContext configuration property: Parser listener.
*
*
* Specifies the parser listener class to use for listening to non-fatal parsing errors.
*
*
See Also:
* {@link org.apache.juneau.parser.Parser.Builder#listener(Class)}
*
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder parserListener(Class extends ParserListener> value) {
if (isNotVoid(value))
parsers.forEach(x -> x.listener(value));
return this;
}
/**
* Resource path.
*
*
* Identifies the URL subpath relative to the parent resource.
*
*
* This setting is critical for the routing of HTTP requests from ascendant to child resources.
*
*
Example:
*
* // Option #1 - Defined via annotation.
* @Rest (path="/myResource" )
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .path("/myResource" );
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .path("/myResource" );
* }
* }
*
*
*
*
Notes:
*
* This annotation is ignored on top-level servlets (i.e. servlets defined in web.xml files).
* Therefore, implementers can optionally specify a path value for documentation purposes.
*
* Typically, this setting is only applicable to resources defined as children through the
* {@link Rest#children() @Rest(children)} annotation.
* However, it may be used in other ways (e.g. defining paths for top-level resources in microservices).
*
* Slashes are trimmed from the path ends.
* As a convention, you may want to start your path with '/' simple because it make it easier to read.
*
* This path is available through the following method:
*
* {@link RestContext#getPath() RestContext.getPath()}
*
*
*
* See Also:
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder path(String value) {
value = trimLeadingSlashes(value);
if (! isEmpty(value))
path = value;
return this;
}
/**
* REST children class.
*
*
* Allows you to extend the {@link RestChildren} class to modify how any of the methods are implemented.
*
*
* The subclass must have a public constructor that takes in any of the following arguments:
*
* {@link RestChildren.Builder} - The builder for the object.
* Any beans found in the specified bean store.
* Any {@link Optional} beans that may or may not be found in the specified bean store.
*
*
* Example:
*
* // Our extended context class
* public MyRestChildren extends RestChildren {
* public MyRestChildren(RestChildren.Builder builder , ARequiredSpringBean bean1 , Optional<AnOptionalSpringBean> bean2 ) {
* super (builder );
* }
*
* // Override any methods.
*
* @Override
* public Optional<RestChildMatch> findMatch(RestCall call ) {
* String path = call .getPathInfo();
* if (path .endsWith("/foo" )) {
* // Do our own special handling.
* }
* return super .findMatch(call );
* }
* }
*
*
* // Option #1 - Defined via annotation.
* @Rest (restChildrenClass=MyRestChildren.class )
* public class MyResource {
* ...
*
* // Option #2 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .restChildrenClass(MyRestChildren.class );
* }
* }
*
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder restChildrenClass(Class extends RestChildren> value) {
childrenClass = value;
return this;
}
/**
* REST operation context class.
*
*
* Allows you to extend the {@link RestOpContext} class to modify how any of the methods are implemented.
*
*
* The subclass must have a public constructor that takes in any of the following arguments:
*
* {@link RestOpContext.Builder} - The builder for the object.
* Any beans found in the specified bean store.
* Any {@link Optional} beans that may or may not be found in the specified bean store.
*
*
* Example:
*
* // Our extended context class that adds a request attribute to all requests.
* // The attribute value is provided by an injected spring bean.
* public MyRestOperationContext extends RestOpContext {
*
* private final Optional<? extends Supplier<Object>> fooSupplier ;
*
* // Constructor that takes in builder and optional injected attribute provider.
* public MyRestOperationContext(RestOpContext.Builder builder , Optional<AnInjectedFooSupplier> fooSupplier ) {
* super (builder );
* this .fooSupplier = fooSupplier .orElseGet(()->null );
* }
*
* // Override the method used to create default request attributes.
* @Override
* protected NamedAttributeMap createDefaultRequestAttributes(Object resource , BeanStore beanStore , Method method , RestContext context ) throws Exception {
* return super
* .createDefaultRequestAttributes(resource , beanStore , method , context )
* .append(NamedAttribute.of ("foo" , ()->fooSupplier .get());
* }
* }
*
*
* // Option #1 - Defined via annotation.
* @Rest (restOpContextClass=MyRestOperationContext.class )
* public class MyResource {
* ...
*
* // Option #2 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .methodContextClass(MyRestOperationContext.class );
* }
*
* @RestGet
* public Object foo(RequestAttributes attributes ) {
* return attributes .get("foo" );
* }
* }
*
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder restOpContextClass(Class extends RestOpContext> value) {
opContextClass = value;
return this;
}
/**
* REST operations class.
*
*
* Allows you to extend the {@link RestOperations} class to modify how any of the methods are implemented.
*
*
* The subclass must have a public constructor that takes in any of the following arguments:
*
* {@link RestOperations.Builder} - The builder for the object.
* Any beans found in the specified bean store.
* Any {@link Optional} beans that may or may not be found in the specified bean store.
*
*
* Example:
*
* // Our extended context class
* public MyRestOperations extends RestOperations {
* public MyRestOperations(RestOperations.Builder builder , ARequiredSpringBean bean1 , Optional<AnOptionalSpringBean> bean2 ) {
* super (builder );
* }
*
* // Override any methods.
*
* @Override
* public RestOpContext findMethod(RestCall call ) throws MethodNotAllowed, PreconditionFailed, NotFound {
* String path = call .getPathInfo();
* if (path .endsWith("/foo" )) {
* // Do our own special handling.
* }
* return super .findMethod(call );
* }
* }
*
*
* // Option #1 - Defined via annotation.
* @Rest (restMethodsClass=MyRestOperations.class )
* public class MyResource {
* ...
*
* // Option #2 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .restMethodsClass(MyRestOperations.class );
* }
* }
*
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder restOperationsClass(Class extends RestOperations> value) {
operationsClass = value;
return this;
}
/**
* RestContext configuration property: Serializer listener.
*
*
* Specifies the serializer listener class to use for listening to non-fatal serialization errors.
*
*
See Also:
* {@link org.apache.juneau.serializer.Serializer.Builder#listener(Class)}
*
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder serializerListener(Class extends SerializerListener> value) {
if (isNotVoid(value))
serializers.forEach(x -> x.listener(value));
return this;
}
/**
* Supported accept media types.
*
*
* Overrides the media types inferred from the serializers that identify what media types can be produced by the resource.
* An example where this might be useful if you have serializers registered that handle media types that you
* don't want exposed in the Swagger documentation.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (produces={"$C{REST/supportedProduces,application/json}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .produces(false , "application/json" )
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .produces(false , "application/json" );
* }
* }
*
*
*
* This affects the returned values from the following:
*
* {@link RestContext#getProduces() RestContext.getProduces()}
* {@link SwaggerProvider#getSwagger(RestContext,Locale)} - Affects produces field.
*
*
* See Also:
* {@link Rest#produces}
* {@link RestOp#produces}
* {@link RestGet#produces}
* {@link RestPut#produces}
* {@link RestPost#produces}
*
*
* @param values The values to add to this setting.
* @return This object.
*/
@FluentSetter
public Builder produces(MediaType...values) {
produces = addAll(produces, values);
return this;
}
/**
* Returns the media types produced by this resource if it's manually specified.
*
* @return The media types.
*/
public Optional> produces() {
return optional(produces);
}
/**
* Supported content media types.
*
*
* Overrides the media types inferred from the parsers that identify what media types can be consumed by the resource.
* An example where this might be useful if you have parsers registered that handle media types that you
* don't want exposed in the Swagger documentation.
*
*
Example:
*
* // Option #1 - Defined via annotation resolving to a config file setting with default value.
* @Rest (consumes={"$C{REST/supportedConsumes,application/json}" })
* public class MyResource {
*
* // Option #2 - Defined via builder passed in through resource constructor.
* public MyResource(RestContext.Builder builder ) throws Exception {
*
* // Using method on builder.
* builder .consumes(false , "application/json" )
* }
*
* // Option #3 - Defined via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .consumes(false , "application/json" );
* }
* }
*
*
*
* This affects the returned values from the following:
*
* {@link RestContext#getConsumes() RestContext.getConsumes()}
*
*
* See Also:
* {@link Rest#consumes}
* {@link RestOp#consumes}
* {@link RestPut#consumes}
* {@link RestPost#consumes}
*
*
* @param values The values to add to this setting.
* @return This object.
*/
@FluentSetter
public Builder consumes(MediaType...values) {
consumes = addAll(consumes, values);
return this;
}
/**
* Returns the media types consumed by this resource if it's manually specified.
*
* @return The media types.
*/
public Optional> consumes() {
return optional(consumes);
}
//
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder annotations(Annotation...values) {
super.annotations(values);
return this;
}
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder apply(AnnotationWorkList work) {
super.apply(work);
return this;
}
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder applyAnnotations(java.lang.Class>...fromClasses) {
super.applyAnnotations(fromClasses);
return this;
}
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder applyAnnotations(Method...fromMethods) {
super.applyAnnotations(fromMethods);
return this;
}
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder cache(Cache value) {
super.cache(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder debug() {
super.debug();
return this;
}
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder debug(boolean value) {
super.debug(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder impl(Context value) {
super.impl(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.Context.Builder */
public Builder type(Class extends org.apache.juneau.Context> value) {
super.type(value);
return this;
}
//
//----------------------------------------------------------------------------------------------------
// Helper methods
//----------------------------------------------------------------------------------------------------
private static MethodList getAnnotatedMethods(Supplier> resource, Class annotation, Predicate predicate) {
Map x = map();
Object r = resource.get();
ClassInfo.ofProxy(r).forEachAllMethodParentFirst(
y -> y.hasAnnotation(annotation),
y -> y.forEachAnnotation(annotation, predicate, z -> x.put(y.getSignature(), y.accessible().inner()))
);
MethodList x2 = MethodList.of(x.values());
return x2;
}
private static boolean isRestBeanMethod(MethodInfo mi) {
RestInject x = mi.getAnnotation(RestInject.class);
return x != null && x.methodScope().length == 0;
}
private static boolean isRestBeanMethod(MethodInfo mi, String name) {
RestInject x = mi.getAnnotation(RestInject.class);
return x != null && x.methodScope().length == 0 && x.name().equals(name);
}
//----------------------------------------------------------------------------------------------------
// Methods inherited from ServletConfig
//----------------------------------------------------------------------------------------------------
@Override /* ServletConfig */
public String getInitParameter(String name) {
return inner == null ? null : inner.getInitParameter(name);
}
@Override /* ServletConfig */
public Enumeration getInitParameterNames() {
return inner == null ? new Vector().elements() : inner.getInitParameterNames();
}
@Override /* ServletConfig */
public ServletContext getServletContext() {
return inner != null ? inner.getServletContext() : parentContext != null ? parentContext.getBuilder().getServletContext() : null;
}
@Override /* ServletConfig */
public String getServletName() {
return inner == null ? null : inner.getServletName();
}
}
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
private final Supplier> resource;
private final Class> resourceClass;
final Builder builder;
private final boolean
allowContentParam,
renderResponseStackTraces;
private final String
clientVersionHeader,
uriAuthority,
uriContext;
private final String path, fullPath;
private final UrlPathMatcher pathMatcher;
private final Set allowedMethodParams, allowedHeaderParams, allowedMethodHeaders;
private final Class extends RestOpArg>[] restOpArgs;
private final BeanContext beanContext;
private final EncoderSet encoders;
private final SerializerSet serializers;
private final ParserSet parsers;
private final HttpPartSerializer partSerializer;
private final HttpPartParser partParser;
private final JsonSchemaGenerator jsonSchemaGenerator;
private final List consumes, produces;
private final HeaderList defaultRequestHeaders, defaultResponseHeaders;
private final NamedAttributeMap defaultRequestAttributes;
private final ResponseProcessor[] responseProcessors;
private final Messages messages;
private final Config config;
private final VarResolver varResolver;
private final RestOperations restOperations;
private final RestChildren restChildren;
private final Logger logger;
private final SwaggerProvider swaggerProvider;
private final BasicHttpException initException;
private final RestContext parentContext;
private final BeanStore beanStore;
private final UriResolution uriResolution;
private final UriRelativity uriRelativity;
private final MethodExecStore methodExecStore;
private final ThrownStore thrownStore;
private final ConcurrentHashMap swaggerCache = new ConcurrentHashMap<>();
private final Instant startTime;
final Charset defaultCharset;
final long maxInput;
final DefaultClassList defaultClasses;
final DefaultSettingsMap defaultSettings;
final BeanStore rootBeanStore;
// Lifecycle methods
private final MethodInvoker[]
postInitMethods,
postInitChildFirstMethods,
startCallMethods,
endCallMethods,
destroyMethods;
private final MethodList
preCallMethods,
postCallMethods;
private final StaticFiles staticFiles;
private final CallLogger callLogger;
private final DebugEnablement debugEnablement;
private final ThreadLocal localSession = new ThreadLocal<>();
// Gets set when postInitChildFirst() gets called.
private final AtomicBoolean initialized = new AtomicBoolean(false);
/**
* Constructor.
*
* @param builder The builder containing the settings for this bean.
* @throws Exception If any initialization problems were encountered.
*/
public RestContext(Builder builder) throws Exception {
super(builder);
startTime = Instant.now();
REGISTRY.put(builder.resourceClass, this);
BasicHttpException _initException = null;
try {
this.builder = builder;
resourceClass = builder.resourceClass;
resource = builder.resource;
parentContext = builder.parentContext;
rootBeanStore = builder.rootBeanStore();
defaultClasses = builder.defaultClasses();
defaultSettings = builder.defaultSettings();
BeanStore bs = beanStore = builder.beanStore();
beanStore
.addBean(BeanStore.class, beanStore)
.addBean(RestContext.class, this)
.addBean(Object.class, resource.get())
.addBean(DefaultSettingsMap.class, defaultSettings)
.addBean(Builder.class, builder)
.addBean(AnnotationWorkList.class, builder.getApplied());
path = builder.path != null ? builder.path : "";
fullPath = (parentContext == null ? "" : (parentContext.fullPath + '/')) + path;
String p = path;
if (! p.endsWith("/*"))
p += "/*";
pathMatcher = UrlPathMatcher.of(p);
allowContentParam = ! builder.disableContentParam;
allowedHeaderParams = newCaseInsensitiveSet(ofNullable(builder.allowedHeaderParams).map(x -> "NONE".equals(x) ? "" : x).orElse(""));
allowedMethodParams = newCaseInsensitiveSet(ofNullable(builder.allowedMethodParams).map(x -> "NONE".equals(x) ? "" : x).orElse(""));
allowedMethodHeaders = newCaseInsensitiveSet(ofNullable(builder.allowedMethodHeaders).map(x -> "NONE".equals(x) ? "" : x).orElse(""));
clientVersionHeader = builder.clientVersionHeader;
defaultCharset = builder.defaultCharset;
maxInput = builder.maxInput;
renderResponseStackTraces = builder.renderResponseStackTraces;
uriContext = builder.uriContext;
uriAuthority = builder.uriAuthority;
uriResolution = builder.uriResolution;
uriRelativity = builder.uriRelativity;
beanContext = bs.add(BeanContext.class, builder.beanContext().build());
encoders = bs.add(EncoderSet.class, builder.encoders().build());
serializers = bs.add(SerializerSet.class, builder.serializers().build());
parsers = bs.add(ParserSet.class, builder.parsers().build());
logger = bs.add(Logger.class, builder.logger());
thrownStore = bs.add(ThrownStore.class, builder.thrownStore().build());
methodExecStore = bs.add(MethodExecStore.class, builder.methodExecStore().thrownStoreOnce(thrownStore).build());
messages = bs.add(Messages.class, builder.messages().build());
varResolver = bs.add(VarResolver.class, builder.varResolver().bean(Messages.class, messages).build());
config = bs.add(Config.class, builder.config().resolving(varResolver.createSession()));
responseProcessors = bs.add(ResponseProcessor[].class, builder.responseProcessors().build().toArray());
callLogger = bs.add(CallLogger.class, builder.callLogger().orElse(null));
partSerializer = bs.add(HttpPartSerializer.class, builder.partSerializer().create());
partParser = bs.add(HttpPartParser.class, builder.partParser().create());
jsonSchemaGenerator = bs.add(JsonSchemaGenerator.class, builder.jsonSchemaGenerator().build());
staticFiles = bs.add(StaticFiles.class, builder.staticFiles().orElse(null));
bs.add(FileFinder.class, staticFiles);
defaultRequestHeaders = bs.add(HeaderList.class, builder.defaultRequestHeaders(), "defaultRequestHeaders");
defaultResponseHeaders = bs.add(HeaderList.class, builder.defaultResponseHeaders(), "defaultResponseHeaders");
defaultRequestAttributes = bs.add(NamedAttributeMap.class, builder.defaultRequestAttributes(), "defaultRequestAttributes");
restOpArgs = builder.restOpArgs().build().asArray();
debugEnablement = bs.add(DebugEnablement.class, builder.debugEnablement().orElse(null));
startCallMethods = builder.startCallMethods().stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new);
endCallMethods = builder.endCallMethods().stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new);
postInitMethods = builder.postInitMethods().stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new);
postInitChildFirstMethods = builder.postInitChildFirstMethods().stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new);
destroyMethods = builder.destroyMethods().stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new);
preCallMethods = builder.preCallMethods();
postCallMethods = builder.postCallMethods();
restOperations = builder.restOperations(this).build();
restChildren = builder.restChildren(this).build();
swaggerProvider = bs.add(SwaggerProvider.class, builder.swaggerProvider().orElse(null));
List opContexts = restOperations.getOpContexts();
produces = builder.produces().orElseGet(
()->{
Set s = opContexts.isEmpty() ? emptySet() : setFrom(opContexts.get(0).getSerializers().getSupportedMediaTypes());
opContexts.forEach(x -> s.retainAll(x.getSerializers().getSupportedMediaTypes()));
return unmodifiable(listFrom(s));
}
);
consumes = builder.consumes().orElseGet(
()->{
Set s = opContexts.isEmpty() ? emptySet() : setFrom(opContexts.get(0).getParsers().getSupportedMediaTypes());
opContexts.forEach(x -> s.retainAll(x.getParsers().getSupportedMediaTypes()));
return unmodifiable(listFrom(s));
}
);
} catch (BasicHttpException e) {
_initException = e;
throw e;
} catch (Exception e) {
_initException = new InternalServerError(e);
throw e;
} finally {
initException = _initException;
}
}
private MethodInvoker toMethodInvoker(Method m) {
return new MethodInvoker(m, getMethodExecStats(m));
}
private Set newCaseInsensitiveSet(String value) {
Set s = new TreeSet<>(String.CASE_INSENSITIVE_ORDER) {
private static final long serialVersionUID = 1L;
@Override
public boolean contains(Object v) {
return v == null ? false : super.contains(v);
}
};
split(value, x -> s.add(x));
return unmodifiable(s);
}
@Override /* Context */
public RestSession.Builder createSession() {
return RestSession.create(this);
}
/**
* Returns the bean store associated with this context.
*
*
* The bean store is used for instantiating child resource classes.
*
* @return The resource resolver associated with this context.
*/
public BeanStore getBeanStore() {
return beanStore;
}
/**
* Returns the bean context associated with this context.
*
* @return The bean store associated with this context.
*/
public BeanContext getBeanContext() {
return beanContext;
}
/**
* Returns the encoders associated with this context.
*
* @return The encoders associated with this context.
*/
public EncoderSet getEncoders() {
return encoders;
}
/**
* Returns the serializers associated with this context.
*
* @return The serializers associated with this context.
*/
public SerializerSet getSerializers() {
return serializers;
}
/**
* Returns the parsers associated with this context.
*
* @return The parsers associated with this context.
*/
public ParserSet getParsers() {
return parsers;
}
/**
* Returns the time statistics gatherer for the specified method.
*
* @param m The method to get statistics for.
* @return The cached time-stats object.
*/
protected MethodExecStats getMethodExecStats(Method m) {
return this.methodExecStore.getStats(m);
}
/**
* Returns the variable resolver for this servlet.
*
*
* Variable resolvers are used to replace variables in property values.
* They can be nested arbitrarily deep.
* They can also return values that themselves contain other variables.
*
*
*
* @Rest (
* messages="nls/Messages" ,
* properties={
* @Property (name="title" ,value="$L{title}" ), // Localized variable in Messages.properties
* @Property (name="javaVendor" ,value="$S{java.vendor,Oracle}" ), // System property with default value
* @Property (name="foo" ,value="bar" ),
* @Property (name="bar" ,value="baz" ),
* @Property (name="v1" ,value="$R{foo}" ), // Request variable. value="bar"
* @Property (name="v1" ,value="$R{foo,bar}" ), // Request variable. value="bar"
* }
* )
* public class MyRestResource extends BasicRestServlet {
*
*
*
* A typical usage pattern involves using variables inside the {@link HtmlDocConfig @HtmlDocConfig} annotation:
*
* @RestGet ("/{name}/*" )
* @HtmlDocConfig (
* navlinks={
* "up: $R{requestParentURI}" ,
* "api: servlet:/api" ,
* "stats: servlet:/stats" ,
* "editLevel: servlet:/editLevel?logger=$A{attribute.name, OFF}"
* }
* header={
* "<h1>$L{MyLocalizedPageTitle}</h1>"
* },
* aside={
* "$F{resources/AsideText.html}"
* }
* )
* public LoggerEntry getLogger(RestRequest req , @Path String name ) throws Exception {
*
*
* See Also:
*
* @return The var resolver in use by this resource.
*/
public VarResolver getVarResolver() {
return varResolver;
}
/**
* Returns the config file associated with this servlet.
*
*
* The config file is identified via one of the following:
*
* {@link Rest#config()}
* {@link RestContext.Builder#config(Config)}
*
*
* @return
* The resolving config file associated with this servlet.
* Never null .
*/
public Config getConfig() {
return config;
}
/**
* Returns the path for this resource as defined by the {@link Rest#path() @Rest(path)} annotation or
* {@link RestContext.Builder#path(String)} method.
*
*
* If path is not specified, returns "" .
*
*
See Also:
* {@link RestContext.Builder#path(String)}
*
*
* @return The servlet path.
*/
public String getPath() {
return path;
}
/**
* Returns the path for this resource as defined by the {@link Rest#path() @Rest(path)} annotation or
* {@link RestContext.Builder#path(String)} method concatenated with those on all parent classes.
*
*
* If path is not specified, returns "" .
*
*
See Also:
* {@link RestContext.Builder#path(String)}
*
*
* @return The full path.
*/
public String getFullPath() {
return fullPath;
}
/**
* Returns the call logger to use for this resource.
*
* See Also:
* {@link RestContext.Builder#callLogger()}
*
*
* @return
* The call logger to use for this resource.
* Never null .
*/
public CallLogger getCallLogger() {
return callLogger;
}
/**
* Returns the resource bundle used by this resource.
*
* @return
* The resource bundle for this resource.
* Never null .
*/
public Messages getMessages() {
return messages;
}
/**
* Returns the Swagger provider used by this resource.
*
* See Also:
* {@link RestContext.Builder#swaggerProvider(Class)}
* {@link RestContext.Builder#swaggerProvider(SwaggerProvider)}
*
*
* @return
* The information provider for this resource.
* Never null .
*/
public SwaggerProvider getSwaggerProvider() {
return swaggerProvider;
}
/**
* Returns the resource object.
*
*
* This is the instance of the class annotated with the {@link Rest @Rest} annotation, usually
* an instance of {@link RestServlet}.
*
* @return
* The resource object.
* Never null .
*/
public Object getResource() {
return resource.get();
}
/**
* Returns the servlet init parameter returned by {@link ServletConfig#getInitParameter(String)}.
*
* @param name The init parameter name.
* @return The servlet init parameter, or null if not found.
*/
public String getServletInitParameter(String name) {
return builder.getInitParameter(name);
}
/**
* Returns the child resources associated with this servlet.
*
* @return
* An unmodifiable map of child resources.
* Keys are the {@link Rest#path() @Rest(path)} annotation defined on the child resource.
*/
public RestChildren getRestChildren() {
return restChildren;
}
/**
* Returns whether it's safe to render stack traces in HTTP responses.
*
* @return true if setting is enabled.
*/
public boolean isRenderResponseStackTraces() {
return renderResponseStackTraces;
}
/**
* Returns whether it's safe to pass the HTTP content as a "content" GET parameter.
*
*
See Also:
* {@link RestContext.Builder#disableContentParam()}
*
*
* @return true if setting is enabled.
*/
public boolean isAllowContentParam() {
return allowContentParam;
}
/**
* Allowed header URL parameters.
*
* See Also:
* {@link Rest#allowedHeaderParams}
* {@link RestContext.Builder#allowedHeaderParams(String)}
*
*
* @return
* The header names allowed to be passed as URL parameters.
* The set is case-insensitive ordered and unmodifiable.
*/
public Set getAllowedHeaderParams() {
return allowedHeaderParams;
}
/**
* Allowed method headers.
*
* See Also:
* {@link Rest#allowedMethodHeaders}
* {@link RestContext.Builder#allowedMethodHeaders(String)}
*
*
* @return
* The method names allowed to be passed as X-Method headers.
* The set is case-insensitive ordered and unmodifiable.
*/
public Set getAllowedMethodHeaders() {
return allowedMethodHeaders;
}
/**
* Allowed method URL parameters.
*
* See Also:
* {@link Rest#allowedMethodParams}
* {@link RestContext.Builder#allowedMethodParams(String)}
*
*
* @return
* The method names allowed to be passed as method URL parameters.
* The set is case-insensitive ordered and unmodifiable.
*/
public Set getAllowedMethodParams() {
return allowedMethodParams;
}
/**
* Returns the name of the client version header name used by this resource.
*
* See Also:
* {@link Rest#clientVersionHeader}
* {@link RestContext.Builder#clientVersionHeader(String)}
*
*
* @return
* The name of the client version header used by this resource.
* Never null .
*/
public String getClientVersionHeader() {
return clientVersionHeader;
}
/**
* Returns the static files associated with this context.
*
* @return
* The static files for this resource.
* Never null .
*/
public StaticFiles getStaticFiles() {
return staticFiles;
}
/**
* Returns the logger associated with this context.
*
* @return
* The logger for this resource.
* Never null .
*/
public Logger getLogger() {
return logger;
}
/**
* Returns the stack trace database associated with this context.
*
* @return
* The stack trace database for this resource.
* Never null .
*/
public ThrownStore getThrownStore() {
return thrownStore;
}
/**
* Returns the HTTP-part parser associated with this resource.
*
* @return
* The HTTP-part parser associated with this resource.
* Never null .
*/
public HttpPartParser getPartParser() {
return partParser;
}
/**
* Returns the HTTP-part serializer associated with this resource.
*
* @return
* The HTTP-part serializer associated with this resource.
* Never null .
*/
public HttpPartSerializer getPartSerializer() {
return partSerializer;
}
/**
* Returns the JSON-Schema generator associated with this resource.
*
* @return
* The HTTP-part serializer associated with this resource.
* Never null .
*/
public JsonSchemaGenerator getJsonSchemaGenerator() {
return jsonSchemaGenerator;
}
/**
* Returns the explicit list of supported accept types for this resource.
*
*
* Consists of the media types for production common to all operations on this class.
*
*
* Can be overridden by {@link RestContext.Builder#produces(MediaType...)}.
*
* @return
* An unmodifiable list of supported Accept header values for this resource.
* Never null .
*/
public List getProduces() {
return produces;
}
/**
* Returns the explicit list of supported content types for this resource.
*
*
* Consists of the media types for consumption common to all operations on this class.
*
*
* Can be overridden by {@link RestContext.Builder#consumes(MediaType...)}.
*
* @return
* An unmodifiable list of supported Content-Type header values for this resource.
* Never null .
*/
public List getConsumes() {
return consumes;
}
/**
* Returns the default request headers for this resource.
*
* See Also:
* {@link RestContext.Builder#defaultRequestHeaders(org.apache.http.Header...)}
*
*
* @return
* The default request headers for this resource in an unmodifiable list.
* Never null .
*/
public HeaderList getDefaultRequestHeaders() {
return defaultRequestHeaders;
}
/**
* Returns the default request attributes for this resource.
*
* See Also:
* {@link RestContext.Builder#defaultRequestAttributes(NamedAttribute...)}
*
*
* @return
* The default request headers for this resource in an unmodifiable list.
* Never null .
*/
public NamedAttributeMap getDefaultRequestAttributes() {
return defaultRequestAttributes;
}
/**
* Returns the default response headers for this resource.
*
* See Also:
* {@link RestContext.Builder#defaultResponseHeaders(org.apache.http.Header...)}
*
*
* @return
* The default response headers for this resource in an unmodifiable list.
* Never null .
*/
public HeaderList getDefaultResponseHeaders() {
return defaultResponseHeaders;
}
/**
* Returns the authority path of the resource.
*
* See Also:
* {@link RestContext.Builder#uriAuthority(String)}
*
*
* @return
* The authority path of this resource.
* If not specified, returns the context path of the ascendant resource.
*/
public String getUriAuthority() {
if (uriAuthority != null)
return uriAuthority;
if (parentContext != null)
return parentContext.getUriAuthority();
return null;
}
/**
* Returns the context path of the resource.
*
* See Also:
* {@link RestContext.Builder#uriContext(String)}
*
*
* @return
* The context path of this resource.
* If not specified, returns the context path of the ascendant resource.
*/
public String getUriContext() {
if (uriContext != null)
return uriContext;
if (parentContext != null)
return parentContext.getUriContext();
return null;
}
/**
* Returns the setting on how relative URIs should be interpreted as relative to.
*
* See Also:
* {@link RestContext.Builder#uriRelativity(UriRelativity)}
*
*
* @return
* The URI-resolution relativity setting value.
* Never null .
*/
public UriRelativity getUriRelativity() {
return uriRelativity;
}
/**
* Returns the setting on how relative URIs should be resolved.
*
* See Also:
* {@link RestContext.Builder#uriResolution(UriResolution)}
*
*
* @return
* The URI-resolution setting value.
* Never null .
*/
public UriResolution getUriResolution() {
return uriResolution;
}
/**
* Returns the REST Java methods defined in this resource.
*
*
* These are the methods annotated with the {@link RestOp @RestOp} annotation.
*
* @return
* An unmodifiable map of Java method names to call method objects.
*/
public RestOperations getRestOperations() {
return restOperations;
}
/**
* Returns the timing statistics on all method executions on this class.
*
* @return The timing statistics on all method executions on this class.
*/
public MethodExecStore getMethodExecStore() {
return methodExecStore;
}
/**
* Gives access to the internal statistics on this context.
*
* @return The context statistics.
*/
public RestContextStats getStats() {
return new RestContextStats(startTime, getMethodExecStore().getStatsByTotalTime());
}
/**
* Returns the resource class type.
*
* @return The resource class type.
*/
public Class> getResourceClass() {
return resourceClass;
}
/**
* Returns the builder that created this context.
*
* @return The builder that created this context.
*/
public ServletConfig getBuilder() {
return builder;
}
/**
* Returns the path matcher for this context.
*
* @return The path matcher for this context.
*/
public UrlPathMatcher getPathMatcher() {
return pathMatcher;
}
/**
* Returns the root bean store for this context.
*
* @return The root bean store for this context.
*/
public BeanStore getRootBeanStore() {
return rootBeanStore;
}
/**
* Returns the swagger for the REST resource.
*
* @param locale The locale of the swagger to return.
* @return The swagger as an {@link Optional}. Never null .
*/
public Optional getSwagger(Locale locale) {
Swagger s = swaggerCache.get(locale);
if (s == null) {
try {
s = swaggerProvider.getSwagger(this, locale);
if (s != null)
swaggerCache.put(locale, s);
} catch (Exception e) {
throw new InternalServerError(e);
}
}
return optional(s);
}
/**
* Finds the {@link RestOpArg} instances to handle resolving objects on the calls to the specified Java method.
*
* @param m The Java method being called.
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* Created by {@link RestContext.Builder#beanStore()}.
* @return The array of resolvers.
*/
protected RestOpArg[] findRestOperationArgs(Method m, BeanStore beanStore) {
MethodInfo mi = MethodInfo.of(m);
List pt = mi.getParamTypes();
RestOpArg[] ra = new RestOpArg[pt.size()];
beanStore = BeanStore.of(beanStore, getResource());
for (int i = 0; i < pt.size(); i++) {
ParamInfo pi = mi.getParam(i);
beanStore.addBean(ParamInfo.class, pi);
for (Class extends RestOpArg> c : restOpArgs) {
try {
ra[i] = beanStore.createBean(RestOpArg.class).type(c).run();
if (ra[i] != null)
break;
} catch (ExecutableException e) {
throw new InternalServerError(e.unwrap(), "Could not resolve parameter {0} on method {1}.", i, mi.getFullName());
}
}
if (ra[i] == null)
throw new InternalServerError("Could not resolve parameter {0} on method {1}.", i, mi.getFullName());
}
return ra;
}
/**
* Returns the list of methods to invoke before the actual REST method is called.
*
* @return The list of methods to invoke before the actual REST method is called.
*/
protected MethodList getPreCallMethods() {
return preCallMethods;
}
/**
* Returns the list of methods to invoke after the actual REST method is called.
*
* @return The list of methods to invoke after the actual REST method is called.
*/
protected MethodList getPostCallMethods() {
return postCallMethods;
}
/**
* The main service method.
*
*
* Subclasses can optionally override this method if they want to tailor the behavior of requests.
*
* @param resource
* The REST servlet or bean that this context defines.
* Note that this bean may not be the same bean used during initialization as it may have been replaced at runtime.
* @param r1 The incoming HTTP servlet request object.
* @param r2 The incoming HTTP servlet response object.
* @throws ServletException General servlet exception.
* @throws IOException Thrown by underlying stream.
*/
public void execute(Object resource, HttpServletRequest r1, HttpServletResponse r2) throws ServletException, IOException {
// Must be careful not to bleed thread-locals.
if (localSession.get() != null)
System.err.println("WARNING: Thread-local call object was not cleaned up from previous request. " + this + ", thread=["+Thread.currentThread().getId()+"]");
RestSession.Builder sb = createSession().resource(resource).req(r1).res(r2).logger(getCallLogger());
try {
if (initException != null)
throw initException;
// If the resource path contains variables (e.g. @Rest(path="/f/{a}/{b}"), then we want to resolve
// those variables and push the servletPath to include the resolved variables. The new pathInfo will be
// the remainder after the new servletPath.
// Only do this for the top-level resource because the logic for child resources are processed next.
if (pathMatcher.hasVars() && parentContext == null) {
String sp = sb.req().getServletPath();
String pi = sb.getPathInfoUndecoded();
UrlPath upi2 = UrlPath.of(pi == null ? sp : sp + pi);
UrlPathMatch uppm = pathMatcher.match(upi2);
if (uppm != null && ! uppm.hasEmptyVars()) {
sb.pathVars(uppm.getVars());
sb.req(
new OverrideableHttpServletRequest(sb.req())
.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
.servletPath(uppm.getPrefix())
);
} else {
RestSession call = sb.build();
call.debug(isDebug(call)).status(SC_NOT_FOUND).finish();
return;
}
}
// If this resource has child resources, try to recursively call them.
Optional childMatch = restChildren.findMatch(sb);
if (childMatch.isPresent()) {
UrlPathMatch uppm = childMatch.get().getPathMatch();
RestContext rc = childMatch.get().getChildContext();
if (! uppm.hasEmptyVars()) {
sb.pathVars(uppm.getVars());
HttpServletRequest childRequest = new OverrideableHttpServletRequest(sb.req())
.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
.servletPath(sb.req().getServletPath() + uppm.getPrefix());
rc.execute(rc.getResource(), childRequest, sb.res());
} else {
RestSession call = sb.build();
call.debug(isDebug(call)).status(SC_NOT_FOUND).finish();
}
return;
}
} catch (Throwable e) {
handleError(sb.build(), convertThrowable(e));
}
RestSession s = sb.build();
try {
localSession.set(s);
s.debug(isDebug(s));
startCall(s);
s.run();
} catch (Throwable e) {
handleError(s, convertThrowable(e));
} finally {
try {
s.finish();
endCall(s);
} finally {
localSession.remove();
}
}
}
private boolean isDebug(RestSession call) {
return debugEnablement.isDebug(this, call.getRequest());
}
/**
* Returns the debug enablement bean for this context.
*
* @return The debug enablement bean for this context.
*/
public DebugEnablement getDebugEnablement() {
return debugEnablement;
}
/**
* The main method for serializing POJOs passed in through the {@link RestResponse#setContent(Object)} method or
* returned by the Java method.
*
*
* Subclasses may override this method if they wish to modify the way the output is rendered or support other output
* formats.
*
*
* The default implementation simply iterates through the response handlers on this resource
* looking for the first one whose {@link ResponseProcessor#process(RestOpSession)} method returns
* true .
*
* @param opSession The HTTP call.
* @throws IOException Thrown by underlying stream.
* @throws BasicHttpException Non-200 response.
* @throws NotImplemented No registered response processors could handle the call.
*/
protected void processResponse(RestOpSession opSession) throws IOException, BasicHttpException, NotImplemented {
// Loop until we find the correct processor for the POJO.
int loops = 5;
for (int i = 0; i < responseProcessors.length; i++) {
int j = responseProcessors[i].process(opSession);
if (j == FINISHED)
return;
if (j == RESTART) {
if (loops-- < 0)
throw new InternalServerError("Too many processing loops.");
i = -1; // Start over.
}
}
Object output = opSession.getResponse().getContent().orElse(null);
throw new NotImplemented("No response processors found to process output of type ''{0}''", className(output));
}
/**
* Method that can be subclassed to allow uncaught throwables to be treated as other types of throwables.
*
*
* The default implementation looks at the throwable class name to determine whether it can be converted to another type:
*
*
* "*AccessDenied*" - Converted to {@link Unauthorized}.
* "*Empty*" ,"*NotFound*" - Converted to {@link NotFound}.
*
*
* @param t The thrown object.
* @return The converted thrown object.
*/
protected Throwable convertThrowable(Throwable t) {
if (t instanceof InvocationTargetException)
t = ((InvocationTargetException)t).getTargetException();
if (t instanceof ExecutableException)
t = ((ExecutableException)t).getTargetException();
if (t instanceof BasicHttpException)
return t;
ClassInfo ci = ClassInfo.of(t);
if (ci.hasAnnotation(Response.class))
return t;
if (ci.isChildOf(ParseException.class) || ci.is(InvalidDataConversionException.class))
return new BadRequest(t);
String n = className(t);
if (n.contains("AccessDenied") || n.contains("Unauthorized"))
return new Unauthorized(t);
if (n.contains("Empty") || n.contains("NotFound"))
return new NotFound(t);
return t;
}
/**
* Handle the case where a matching method was not found.
*
*
* Subclasses can override this method to provide a 2nd-chance for specifying a response.
* The default implementation will simply throw an exception with an appropriate message.
*
* @param session The HTTP call.
* @throws Exception Any exception can be thrown.
*/
protected void handleNotFound(RestSession session) throws Exception {
String pathInfo = session.getPathInfo();
String methodUC = session.getMethod();
int rc = session.getStatus();
String onPath = pathInfo == null ? " on no pathInfo" : String.format(" on path '%s'", pathInfo);
if (rc == SC_NOT_FOUND)
throw new NotFound("Method ''{0}'' not found on resource with matching pattern{1}.", methodUC, onPath);
else if (rc == SC_PRECONDITION_FAILED)
throw new PreconditionFailed("Method ''{0}'' not found on resource{1} with matching matcher.", methodUC, onPath);
else if (rc == SC_METHOD_NOT_ALLOWED)
throw new MethodNotAllowed("Method ''{0}'' not found on resource{1}.", methodUC, onPath);
else
throw new ServletException("Invalid method response: " + rc, session.getException());
}
/**
* Method for handling response errors.
*
*
* Subclasses can override this method to provide their own custom error response handling.
*
* @param session The rest call.
* @param e The exception that occurred.
* @throws IOException Can be thrown if a problem occurred trying to write to the output stream.
*/
protected synchronized void handleError(RestSession session, Throwable e) throws IOException {
session.exception(e);
if (session.isDebug())
e.printStackTrace();
int code = 500;
ClassInfo ci = ClassInfo.of(e);
StatusCode r = ci.getAnnotation(StatusCode.class);
if (r != null)
if (r.value().length > 0)
code = r.value()[0];
BasicHttpException e2 = (e instanceof BasicHttpException ? (BasicHttpException)e : new BasicHttpException(code, e));
HttpServletRequest req = session.getRequest();
HttpServletResponse res = session.getResponse();
Throwable t = e2.getRootCause();
if (t != null) {
Thrown t2 = thrown(t);
res.setHeader(t2.getName(), t2.getValue());
}
try {
res.setContentType("text/plain");
res.setHeader("Content-Encoding", "identity");
int statusCode = e2.getStatusLine().getStatusCode();
res.setStatus(statusCode);
PrintWriter w = null;
try {
w = res.getWriter();
} catch (IllegalStateException x) {
w = new PrintWriter(new OutputStreamWriter(res.getOutputStream(), UTF8));
}
try (PrintWriter w2 = w) {
String httpMessage = RestUtils.getHttpResponseText(statusCode);
if (httpMessage != null)
w2.append("HTTP ").append(String.valueOf(statusCode)).append(": ").append(httpMessage).append("\n\n");
if (isRenderResponseStackTraces())
e.printStackTrace(w2);
else
w2.append(e2.getFullStackMessage(true));
}
} catch (Exception e1) {
req.setAttribute("Exception", e1);
}
}
/**
* Called at the start of a request to invoke all {@link RestStartCall} methods.
*
* @param session The current request.
* @throws BasicHttpException If thrown from call methods.
*/
protected void startCall(RestSession session) throws BasicHttpException {
for (MethodInvoker x : startCallMethods) {
try {
x.invoke(session.getBeanStore(), session.getContext().getResource());
} catch (IllegalAccessException|IllegalArgumentException e) {
throw new InternalServerError(e, "Error occurred invoking start-call method ''{0}''.", x.getFullName());
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof BasicHttpException)
throw (BasicHttpException)t;
throw new InternalServerError(t);
}
}
}
/**
* Called during a request to invoke all {@link RestPreCall} methods.
*
* @param session The current request.
* @throws Throwable If thrown from call methods.
*/
protected void preCall(RestOpSession session) throws Throwable {
for (RestOpInvoker m : session.getContext().getPreCallMethods())
m.invoke(session);
}
/**
* Called during a request to invoke all {@link RestPostCall} methods.
*
* @param session The current request.
* @throws Throwable If thrown from call methods.
*/
protected void postCall(RestOpSession session) throws Throwable {
for (RestOpInvoker m : session.getContext().getPostCallMethods())
m.invoke(session);
}
/**
* Called at the end of a request to invoke all {@link RestEndCall} methods.
*
*
* This is the very last method called in {@link #execute(Object, HttpServletRequest, HttpServletResponse)}.
*
* @param session The current request.
*/
protected void endCall(RestSession session) {
for (MethodInvoker x : endCallMethods) {
try {
x.invoke(session.getBeanStore(), session.getResource());
} catch (Exception e) {
getLogger().log(Level.WARNING, unwrap(e), ()->format("Error occurred invoking finish-call method ''{0}''.", x.getFullName()));
}
}
}
/**
* Called during servlet initialization to invoke all {@link RestPostInit} child-last methods.
*
* @return This object.
* @throws ServletException Error occurred.
*/
public synchronized RestContext postInit() throws ServletException {
if (initialized.get())
return this;
Object resource = getResource();
MethodInfo mi = ClassInfo.of(getResource()).getPublicMethod(
x -> x.hasName("setContext")
&& x.hasParamTypes(RestContext.class)
);
if (mi != null) {
try {
mi.accessible().invoke(resource, this);
} catch (ExecutableException e) {
throw new ServletException(e.unwrap());
}
}
for (MethodInvoker x : postInitMethods) {
try {
x.invoke(beanStore, getResource());
} catch (Exception e) {
throw new ServletException(unwrap(e));
}
}
restChildren.postInit();
return this;
}
/**
* Called during servlet initialization to invoke all {@link RestPostInit} child-first methods.
*
* @return This object.
* @throws ServletException Error occurred.
*/
public RestContext postInitChildFirst() throws ServletException {
if (initialized.get())
return this;
restChildren.postInitChildFirst();
for (MethodInvoker x : postInitChildFirstMethods) {
try {
x.invoke(beanStore, getResource());
} catch (Exception e) {
throw new ServletException(unwrap(e));
}
}
initialized.set(true);
return this;
}
/**
* Called during servlet destruction to invoke all {@link RestDestroy} methods.
*/
public void destroy() {
for (MethodInvoker x : destroyMethods) {
try {
x.invoke(beanStore, getResource());
} catch (Exception e) {
getLogger().log(Level.WARNING, unwrap(e), ()->format("Error occurred invoking servlet-destroy method ''{0}''.", x.getFullName()));
}
}
restChildren.destroy();
}
/**
* Returns the HTTP call for the current request.
*
* @return The HTTP call for the current request, never null ?
* @throws InternalServerError If no active request exists on the current thread.
*/
public RestSession getLocalSession() {
RestSession rc = localSession.get();
if (rc == null)
throw new InternalServerError("No active request on current thread.");
return rc;
}
// /**
// * If the specified object is annotated with {@link Response}, this returns the response metadata about that object.
// *
// * @param o The object to check.
// * @return The response metadata, or null if it wasn't annotated with {@link Response}.
// */
// public ResponseBeanMeta getResponseBeanMeta(Object o) {
// if (o == null)
// return null;
// Class> c = o.getClass();
// ResponseBeanMeta rbm = responseBeanMetas.get(c);
// if (rbm == null) {
// rbm = ResponseBeanMeta.create(c, getAnnotations());
// if (rbm == null)
// rbm = ResponseBeanMeta.NULL;
// responseBeanMetas.put(c, rbm);
// }
// if (rbm == ResponseBeanMeta.NULL)
// return null;
// return rbm;
// }
//
/**
* Returns the annotations applied to this context.
*
* @return The annotations applied to this context.
*/
public AnnotationWorkList getAnnotations() {
return builder.getApplied();
}
//-----------------------------------------------------------------------------------------------------------------
// Helper methods
//-----------------------------------------------------------------------------------------------------------------
private Throwable unwrap(Throwable t) {
if (t instanceof InvocationTargetException) {
return ((InvocationTargetException)t).getTargetException();
}
return t;
}
static ServletException servletException(String msg, Object...args) {
return new ServletException(format(msg, args));
}
static ServletException servletException(Throwable t, String msg, Object...args) {
return new ServletException(format(msg, args), t);
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* Context */
protected JsonMap properties() {
return filteredMap()
.append("allowContentParam", allowContentParam)
.append("allowedMethodHeader", allowedMethodHeaders)
.append("allowedMethodParams", allowedMethodParams)
.append("allowedHeaderParams", allowedHeaderParams)
.append("beanStore", beanStore)
.append("clientVersionHeader", clientVersionHeader)
.append("consumes", consumes)
.append("defaultRequestHeaders", defaultRequestHeaders)
.append("defaultResponseHeaders", defaultResponseHeaders)
.append("restOpArgs", restOpArgs)
.append("partParser", partParser)
.append("partSerializer", partSerializer)
.append("produces", produces)
.append("renderResponseStackTraces", renderResponseStackTraces)
.append("responseProcessors", responseProcessors)
.append("staticFiles", staticFiles)
.append("swaggerProvider", swaggerProvider)
.append("uriAuthority", uriAuthority)
.append("uriContext", uriContext)
.append("uriRelativity", uriRelativity)
.append("uriResolution", uriResolution);
}
}