blob: d6db9ba64d5854de8778fdf152e97733d2e4d853 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.rest;
import static org.apache.juneau.internal.ThrowableUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.internal.StringUtils.firstNonEmpty;
import static org.apache.juneau.collections.JsonMap.*;
import static org.apache.juneau.http.HttpHeaders.*;
import static org.apache.juneau.http.HttpParts.*;
import static org.apache.juneau.httppart.HttpPartType.*;
import static org.apache.juneau.rest.HttpRuntimeException.*;
import static org.apache.juneau.rest.util.RestUtils.*;
import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.nio.charset.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.encoders.*;
import org.apache.juneau.http.annotation.*;
import org.apache.juneau.http.annotation.Header;
import org.apache.juneau.http.header.*;
import org.apache.juneau.http.part.*;
import org.apache.juneau.http.remote.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.httppart.bean.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.internal.HttpUtils;
import org.apache.juneau.jsonschema.*;
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.converter.*;
import org.apache.juneau.rest.debug.*;
import org.apache.juneau.rest.guard.*;
import org.apache.juneau.rest.httppart.*;
import org.apache.juneau.rest.logger.*;
import org.apache.juneau.http.response.*;
import org.apache.juneau.rest.matcher.*;
import org.apache.juneau.rest.swagger.*;
import org.apache.juneau.rest.util.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;
import org.apache.juneau.utils.*;
/**
* Represents a single Java servlet/resource method annotated with {@link RestOp @RestOp}.
*
* <ul class='notes'>
* <li class='note'>This class is thread safe and reusable.
* </ul>
*
* <ul class='seealso'>
* <li class='link'>{@doc jrs.RestOpContext}
* <li class='extlink'>{@source}
* </ul>
*/
public class RestOpContext extends Context implements Comparable<RestOpContext> {
//-------------------------------------------------------------------------------------------------------------------
// Static
//-------------------------------------------------------------------------------------------------------------------
/** Represents a null value for the {@link RestOp#contextClass()} annotation.*/
public static final class Void extends RestOpContext {
private Void(Builder builder) throws Exception {
super(builder);
}
}
/**
* Creates a new builder for this object.
*
* @param method The Java method this context belongs to.
* @param context The Java class context.
* @return A new builder.
*/
public static Builder create(java.lang.reflect.Method method, RestContext context) {
return new Builder(method, context);
}
//-------------------------------------------------------------------------------------------------------------------
// Builder
//-------------------------------------------------------------------------------------------------------------------
/**
* Builder class.
*/
@FluentSetters
public static class Builder extends Context.Builder {
RestContext restContext;
RestContext.Builder parent;
Method restMethod;
String httpMethod, clientVersion;
Enablement debug;
List<String> path;
private RestConverterList.Builder converters;
private BeanContext.Builder beanContext;
private RestGuardList.Builder guards;
private EncoderSet.Builder encoders;
private SerializerSet.Builder serializers;
private ParserSet.Builder parsers;
private HttpPartSerializer.Creator partSerializer;
private HttpPartParser.Creator partParser;
private RestMatcherList.Builder matchers;
private JsonSchemaGenerator.Builder jsonSchemaGenerator;
PartList.Builder defaultRequestFormData, defaultRequestQueryData;
NamedAttributeList.Builder defaultRequestAttributes;
HeaderList.Builder defaultRequestHeaders, defaultResponseHeaders;
RestMatcherList.Builder restMatchers;
List<MediaType> produces, consumes;
Set<String> roleGuard, rolesDeclared;
boolean dotAll;
Charset defaultCharset;
Long maxInput;
private BeanStore beanStore;
@Override /* Context.Builder */
public Builder copy() {
throw new NoSuchMethodError("Not implemented.");
}
@Override /* BeanContext.Builder */
public RestOpContext build() {
try {
return beanStore.createBean(RestOpContext.class).type(getType().orElse(getDefaultImplClass())).builder(RestOpContext.Builder.class, this).run();
} catch (Exception e) {
throw toHttpException(e, InternalServerError.class);
}
}
/**
* Specifies the default implementation class if not specified via {@link #type(Class)}.
*
* @return The default implementation class if not specified via {@link #type(Class)}.
*/
protected Class<? extends RestOpContext> getDefaultImplClass() {
return RestOpContext.class;
}
Builder(java.lang.reflect.Method method, RestContext context) {
this.restContext = context;
this.parent = context.builder;
this.restMethod = method;
this.beanStore = BeanStore
.of(context.getRootBeanStore(), context.builder.resource().get())
.addBean(java.lang.reflect.Method.class, method);
MethodInfo mi = MethodInfo.of(context.getResourceClass(), method);
try {
VarResolver vr = context.getVarResolver();
VarResolverSession vrs = vr.createSession();
AnnotationWorkList work = AnnotationWorkList.of(vrs, mi.getAnnotationList(CONTEXT_APPLY_FILTER));
apply(work);
if (context.builder.beanContext().canApply(work))
beanContext().apply(work);
if (context.builder.serializers().canApply(work))
serializers().apply(work);
if (context.builder.parsers().canApply(work))
parsers().apply(work);
if (context.builder.partSerializer().canApply(work))
partSerializer().apply(work);
if (context.builder.partParser().canApply(work))
partParser().apply(work);
if (context.builder.jsonSchemaGenerator().canApply(work))
jsonSchemaGenerator().apply(work);
processParameterAnnotations();
} catch (Exception e) {
throw toHttpException(e, InternalServerError.class);
}
}
/**
* 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 final Supplier<?> resource() {
return restContext.builder.resource();
}
/**
* Returns the default classes list.
*
* <p>
* This defines the implementation classes for a variety of bean types.
*
* <p>
* 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.
*
* <p>
* 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 classes list for this builder.
*/
public final DefaultClassList defaultClasses() {
return restContext.builder.defaultClasses();
}
//-----------------------------------------------------------------------------------------------------------------
// beanStore
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns access to the bean store being used by this builder.
*
* <p>
* Can be used to add more beans to the bean store.
*
* @return The bean store being used by this builder.
*/
public final BeanStore beanStore() {
return beanStore;
}
//-----------------------------------------------------------------------------------------------------------------
// beanContext
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the bean context sub-builder.
*
* @return The bean context sub-builder.
*/
public final BeanContext.Builder beanContext() {
if (beanContext == null)
beanContext = createBeanContext(beanStore(), parent, resource());
return beanContext;
}
/**
* Applies an operation to the bean context sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .beanContext(<jv>x</jv> -&gt; <jv>x</jv>.ignoreUnknownBeanProperties())
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder beanContext(Consumer<BeanContext.Builder> operation) {
operation.accept(beanContext());
return this;
}
/**
* Instantiates the bean context sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @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, RestContext.Builder parent, Supplier<?> resource) {
// Default value.
Value<BeanContext.Builder> v = Value.of(
parent.beanContext().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(BeanContext.Builder.class, v.get())
.createMethodFinder(BeanContext.Builder.class, resource)
.find("createBeanContext", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(BeanContext.Builder.class, v.get())
.createMethodFinder(BeanContext.class, resource)
.find("createBeanContext", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
final Optional<BeanContext> getBeanContext() {
return optional(beanContext).map(x -> x.build());
}
//-----------------------------------------------------------------------------------------------------------------
// encoders
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the encoder group sub-builder.
*
* @return The encoder group sub-builder.
*/
public final EncoderSet.Builder encoders() {
if (encoders == null)
encoders = createEncoders(beanStore(), parent, resource());
return encoders;
}
/**
* Applies an operation to the encoder group sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .encoders(<jv>x</jv> -&gt; <jv>x</jv>.add(MyEncoder.<jk>class</jk>))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder encoders(Consumer<EncoderSet.Builder> operation) {
operation.accept(encoders());
return this;
}
/**
* Instantiates the encoder group sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new encoder group sub-builder.
*/
protected EncoderSet.Builder createEncoders(BeanStore beanStore, RestContext.Builder parent, Supplier<?> resource) {
// Default value.
Value<EncoderSet.Builder> v = Value.of(
parent.encoders().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(EncoderSet.Builder.class, v.get())
.createMethodFinder(EncoderSet.Builder.class, resource)
.find("createEncoders", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(EncoderSet.Builder.class, v.get())
.createMethodFinder(EncoderSet.class, resource)
.find("createEncoders", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
final Optional<EncoderSet> getEncoders() {
return optional(encoders).map(x -> x.build());
}
//-----------------------------------------------------------------------------------------------------------------
// serializers
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the serializer group sub-builder.
*
* @return The serializer group sub-builder.
*/
public final SerializerSet.Builder serializers() {
if (serializers == null)
serializers = createSerializers(beanStore(), parent, resource());
return serializers;
}
/**
* Applies an operation to the serializer group sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .serializers(<jv>x</jv> -&gt; <jv>x</jv>.add(MySerializer.<jk>class</jk>))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder serializers(Consumer<SerializerSet.Builder> operation) {
operation.accept(serializers());
return this;
}
/**
* Instantiates the serializer group sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new serializer group sub-builder.
*/
protected SerializerSet.Builder createSerializers(BeanStore beanStore, RestContext.Builder parent, Supplier<?> resource) {
// Default value.
Value<SerializerSet.Builder> v = Value.of(
parent.serializers().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(SerializerSet.Builder.class, v.get())
.createMethodFinder(SerializerSet.Builder.class, resource)
.find("createSerializers", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(SerializerSet.Builder.class, v.get())
.createMethodFinder(SerializerSet.class, resource)
.find("createSerializers", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
final Optional<SerializerSet> getSerializers() {
return optional(serializers).map(x -> x.build());
}
//-----------------------------------------------------------------------------------------------------------------
// parsers
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the parser group sub-builder.
*
* @return The parser group sub-builder.
*/
public final ParserSet.Builder parsers() {
if (parsers == null)
parsers = createParsers(beanStore(), parent, resource());
return parsers;
}
/**
* Applies an operation to the parser group sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .parsers(<jv>x</jv> -&gt; <jv>x</jv>.add(MyParser.<jk>class</jk>))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder parsers(Consumer<ParserSet.Builder> operation) {
operation.accept(parsers());
return this;
}
/**
* Instantiates the parser group sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new parser group sub-builder.
*/
protected ParserSet.Builder createParsers(BeanStore beanStore, RestContext.Builder parent, Supplier<?> resource) {
// Default value.
Value<ParserSet.Builder> v = Value.of(
parent.parsers().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(ParserSet.Builder.class, v.get())
.createMethodFinder(ParserSet.Builder.class, resource)
.find("createParsers", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(ParserSet.Builder.class, v.get())
.createMethodFinder(ParserSet.class, resource)
.find("createParsers", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
final Optional<ParserSet> getParsers() {
return optional(parsers).map(x -> x.build());
}
//-----------------------------------------------------------------------------------------------------------------
// partSerializer
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the part serializer sub-builder.
*
* @return The part serializer sub-builder.
*/
public final HttpPartSerializer.Creator partSerializer() {
if (partSerializer == null)
partSerializer = createPartSerializer(beanStore(), parent, resource());
return partSerializer;
}
/**
* Applies an operation to the part serializer sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .partSerializer(<jv>x</jv> -&gt; <jv>x</jv>.builder(OpenApiSerializer.Builder.<jk>class</jk>, <jv>y</jv> -&gt; <jv>y</jv>.sortProperties()))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder partSerializer(Consumer<HttpPartSerializer.Creator> operation) {
operation.accept(partSerializer());
return this;
}
/**
* Instantiates the part serializer sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @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, RestContext.Builder parent, Supplier<?> resource) {
// Default value.
Value<HttpPartSerializer.Creator> v = Value.of(
parent.partSerializer().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(HttpPartSerializer.Creator.class, v.get())
.createMethodFinder(HttpPartSerializer.Creator.class, resource)
.find("createPartSerializer", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(HttpPartSerializer.Creator.class, v.get())
.createMethodFinder(HttpPartSerializer.class, resource)
.find("createPartSerializer", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
final Optional<HttpPartSerializer> getPartSerializer() {
return optional(partSerializer).map(x -> x.create());
}
//-----------------------------------------------------------------------------------------------------------------
// partParser
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the part parser sub-builder.
*
* @return The part parser sub-builder.
*/
public final HttpPartParser.Creator partParser() {
if (partParser == null)
partParser = createPartParser(beanStore(), parent, resource());
return partParser;
}
/**
* Applies an operation to the part parser sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .partParser(<jv>x</jv> -&gt; <jv>x</jv>.builder(OpenApiParser.Builder.<jk>class</jk>, <jv>y</jv> -&gt; <jv>y</jv>.ignoreUnknownBeanProperties()))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder partParser(Consumer<HttpPartParser.Creator> operation) {
operation.accept(partParser());
return this;
}
/**
* Instantiates the part parser sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @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, RestContext.Builder parent, Supplier<?> resource) {
// Default value.
Value<HttpPartParser.Creator> v = Value.of(
parent.partParser().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(HttpPartParser.Creator.class, v.get())
.createMethodFinder(HttpPartParser.Creator.class, resource)
.find("createPartParser", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(HttpPartParser.Creator.class, v.get())
.createMethodFinder(HttpPartParser.class, resource)
.find("createPartParser", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
final Optional<HttpPartParser> getPartParser() {
return optional(partParser).map(x -> x.create());
}
//-----------------------------------------------------------------------------------------------------------------
// jsonSchemaGenerator
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the JSON schema generator sub-builder.
*
* @return The JSON schema generator sub-builder.
*/
public final JsonSchemaGenerator.Builder jsonSchemaGenerator() {
if (jsonSchemaGenerator == null)
jsonSchemaGenerator = createJsonSchemaGenerator(beanStore(), parent, resource());
return jsonSchemaGenerator;
}
/**
* Applies an operation to the JSON schema generator sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .jsonSchemaGenerator(<jv>x</jv> -&gt; <jv>x</jv>.allowNestedExamples()))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder jsonSchemaGenerator(Consumer<JsonSchemaGenerator.Builder> operation) {
operation.accept(jsonSchemaGenerator());
return this;
}
/**
* Instantiates the JSON schema generator sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @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, RestContext.Builder parent, Supplier<?> resource) {
// Default value.
Value<JsonSchemaGenerator.Builder> v = Value.of(
parent.jsonSchemaGenerator().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(JsonSchemaGenerator.Builder.class, v.get())
.createMethodFinder(JsonSchemaGenerator.Builder.class, resource)
.find("createJsonSchemaGenerator", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(JsonSchemaGenerator.Builder.class, v.get())
.createMethodFinder(JsonSchemaGenerator.class, resource)
.find("createJsonSchemaGenerator", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
final Optional<JsonSchemaGenerator> getJsonSchemaGenerator() {
return optional(jsonSchemaGenerator).map(x -> x.build());
}
//-----------------------------------------------------------------------------------------------------------------
// converters
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the response converter list sub-builder.
*
* @return The response converter list sub-builder.
*/
public final RestConverterList.Builder converters() {
if (converters == null)
converters = createConverters(beanStore(), resource());
return converters;
}
/**
* Applies an operation to the response converter list sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .converters(<jv>x</jv> -&gt; <jv>x</jv>.add(MyConverter.<jk>class</jk>)))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder converters(Consumer<RestConverterList.Builder> operation) {
operation.accept(converters());
return this;
}
/**
* Instantiates the response converter list sub-builder.
*
* <p>
* Associates one or more {@link RestConverter converters} with a resource class.
* <br>These converters get called immediately after execution of the REST method in the same order specified in the
* annotation.
* <br>The object passed into this converter is the object returned from the Java method or passed into
* the {@link RestResponse#setContent(Object)} method.
*
* <p>
* Can be used for performing post-processing on the response object before serialization.
*
* <p>
* When multiple converters are specified, they're executed in the order they're specified in the annotation
* (e.g. first the results will be traversed, then the resulting node will be searched/sorted).
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* <jc>// Our converter.</jc>
* <jk>public class</jk> MyConverter <jk>implements</jk> RestConverter {
* <ja>@Override</ja>
* <jk>public</jk> Object convert(RestRequest <jv>req</jv>, Object <jv>object</jv>) {
* <jc>// Do something with object and return another object.</jc>
* <jc>// Or just return the same object for a no-op.</jc>
* }
* }
*
* <jc>// Option #1 - Registered via annotation resolving to a config file setting with default value.</jc>
* <ja>@Rest</ja>(converters={MyConverter.<jk>class</jk>})
* <jk>public class</jk> MyResource {
*
* <jc>// Option #2 - Registered via builder passed in through resource constructor.</jc>
* <jk>public</jk> MyResource(RestContext.Builder <jv>builder</jv>) <jk>throws</jk> Exception {
*
* <jc>// Using method on builder.</jc>
* <jv>builder</jv>.converters(MyConverter.<jk>class</jk>);
*
* <jc>// Pass in an instance instead.</jc>
* <jv>builder</jv>.converters(<jk>new</jk> MyConverter());
* }
*
* <jc>// Option #3 - Registered via builder passed in through init method.</jc>
* <ja>@RestHook</ja>(<jsf>INIT</jsf>)
* <jk>public void</jk> init(RestContext.Builder <jv>builder</jv>) <jk>throws</jk> Exception {
* <jv>builder</jv>.converters(MyConverter.<jk>class</jk>);
* }
* }
* </p>
*
* <ul class='notes'>
* <li class='note'>
* When defined as a class, the implementation must have one of the following constructors:
* <ul>
* <li><code><jk>public</jk> T(BeanContext)</code>
* <li><code><jk>public</jk> T()</code>
* <li><code><jk>public static</jk> T <jsm>create</jsm>(RestContext)</code>
* <li><code><jk>public static</jk> T <jsm>create</jsm>()</code>
* </ul>
* <li class='note'>
* Inner classes of the REST resource class are allowed.
* </ul>
*
* <ul class='seealso'>
* <li class='jc'>{@link Traversable} - Allows URL additional path info to address individual elements in a POJO tree.
* <li class='jc'>{@link Queryable} - Allows query/view/sort functions to be performed on POJOs.
* <li class='jc'>{@link Introspectable} - Allows Java public methods to be invoked on the returned POJOs.
* <li class='ja'>{@link Rest#converters()}
* <li class='link'>{@doc jrs.Converters}
* </ul>
*
* @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 converter list sub-builder.
*/
protected RestConverterList.Builder createConverters(BeanStore beanStore, Supplier<?> resource) {
// Default value.
Value<RestConverterList.Builder> v = Value.of(
RestConverterList
.create(beanStore)
);
// Specify the implementation class if its set as a default.
defaultClasses()
.get(RestConverterList.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from bean store.
beanStore
.getBean(RestConverterList.class)
.ifPresent(x->v.get().impl(x));
// Replace with builder from: public [static] RestConverterList.Builder createConverters(<args>)
beanStore
.createMethodFinder(RestConverterList.Builder.class)
.addBean(RestConverterList.Builder.class, v.get())
.find("createConverters")
.run(x -> v.set(x));
// Replace with bean from: public [static] RestConverterList createConverters(<args>)
beanStore
.createMethodFinder(RestConverterList.class)
.addBean(RestConverterList.Builder.class, v.get())
.find("createConverters")
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// guards
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the guard list sub-builder.
*
* @return The guard list sub-builder.
*/
public final RestGuardList.Builder guards() {
if (guards == null)
guards = createGuards(beanStore(), resource());
return guards;
}
/**
* Applies an operation to the guard list sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .guards(<jv>x</jv> -&gt; <jv>x</jv>.add(MyGuard.<jk>class</jk>)))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder guards(Consumer<RestGuardList.Builder> operation) {
operation.accept(guards());
return this;
}
/**
* Instantiates the guard list sub-builder.
*
* <p>
* Instantiates based on the following logic:
* <ul>
* <li>Looks for guards set via any of the following:
* <ul>
* <li>{@link RestOpContext.Builder#guards()}}
* <li>{@link RestOp#guards()}.
* <li>{@link Rest#guards()}.
* </ul>
* <li>Looks for a static or non-static <c>createGuards()</c> method that returns <c>{@link RestGuard}[]</c> on the
* resource class with any of the following arguments:
* <ul>
* <li>{@link Method} - The Java method this context belongs to.
* <li>{@link RestContext}
* <li>{@link BeanStore}
* <li>Any {@doc juneau-rest-server-springboot injected beans}.
* </ul>
* <li>Resolves it via the bean store registered in this context.
* <li>Instantiates a <c>RestGuard[0]</c>.
* </ul>
*
* @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 guard list sub-builder.
*/
protected RestGuardList.Builder createGuards(BeanStore beanStore, Supplier<?> resource) {
// Default value.
Value<RestGuardList.Builder> v = Value.of(
RestGuardList
.create(beanStore)
);
// Specify the implementation class if its set as a default.
defaultClasses()
.get(RestGuardList.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from bean store.
beanStore
.getBean(RestGuardList.class)
.ifPresent(x->v.get().impl(x));
// Replace with builder from: public [static] RestGuardList.Builder createGuards(<args>)
beanStore
.createMethodFinder(RestGuardList.Builder.class)
.addBean(RestGuardList.Builder.class, v.get())
.find("createGuards")
.run(x -> v.set(x));
// Replace with bean from: public [static] RestGuardList createGuards(<args>)
beanStore
.createMethodFinder(RestGuardList.class)
.addBean(RestGuardList.Builder.class, v.get())
.find("createGuards")
.run(x -> v.get().impl(x));
return v.get();
}
final RestGuardList getGuards() {
RestGuardList.Builder b = guards();
Set<String> roleGuard = optional(this.roleGuard).orElseGet(()->set());
for (String rg : roleGuard) {
try {
b.append(new RoleBasedRestGuard(rolesDeclared, rg));
} catch (java.text.ParseException e1) {
throw asRuntimeException(e1);
}
}
return guards.build();
}
//-----------------------------------------------------------------------------------------------------------------
// matchers
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the matcher list sub-builder.
*
* @return The matcher list sub-builder.
*/
public final RestMatcherList.Builder matchers() {
if (matchers == null)
matchers = createMatchers(beanStore(), resource());
return matchers;
}
/**
* Applies an operation to the matcher list sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .matchers(<jv>x</jv> -&gt; <jv>x</jv>.add(MyMatcher.<jk>class</jk>)))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder matchers(Consumer<RestMatcherList.Builder> operation) {
operation.accept(matchers());
return this;
}
/**
* Instantiates the matcher list sub-builder.
*
* <p>
* Associates one or more {@link RestMatcher RestMatchers} with the specified method.
*
* <p>
* If multiple matchers are specified, <b>ONE</b> matcher must pass.
* <br>Note that this is different than guards where <b>ALL</b> guards needs to pass.
*
* <ul class='notes'>
* <li class='note'>
* When defined as a class, the implementation must have one of the following constructors:
* <ul>
* <li><code><jk>public</jk> T(RestContext)</code>
* <li><code><jk>public</jk> T()</code>
* <li><code><jk>public static</jk> T <jsm>create</jsm>(RestContext)</code>
* <li><code><jk>public static</jk> T <jsm>create</jsm>()</code>
* </ul>
* <li class='note'>
* Inner classes of the REST resource class are allowed.
* </ul>
*
* <ul class='seealso'>
* <li class='ja'>{@link RestOp#matchers()}
* <li class='ja'>{@link RestGet#matchers()}
* <li class='ja'>{@link RestPut#matchers()}
* <li class='ja'>{@link RestPost#matchers()}
* <li class='ja'>{@link RestDelete#matchers()}
* </ul>
*
* <p>
* Instantiates based on the following logic:
* <ul>
* <li>Looks for matchers set via any of the following:
* <ul>
* <li>{@link RestOp#matchers()}.
* </ul>
* <li>Looks for a static or non-static <c>createMatchers()</c> method that returns <c>{@link RestMatcher}[]</c> on the
* resource class with any of the following arguments:
* <ul>
* <li>{@link java.lang.reflect.Method} - The Java method this context belongs to.
* <li>{@link RestContext}
* <li>{@link BeanStore}
* <li>Any {@doc juneau-rest-server-springboot injected beans}.
* </ul>
* <li>Resolves it via the bean store registered in this context.
* <li>Instantiates a <c>RestMatcher[0]</c>.
* </ul>
*
* @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 matcher list sub-builder.
*/
protected RestMatcherList.Builder createMatchers(BeanStore beanStore, Supplier<?> resource) {
// Default value.
Value<RestMatcherList.Builder> v = Value.of(
RestMatcherList
.create(beanStore)
);
// Specify the implementation class if its set as a default.
defaultClasses()
.get(RestMatcherList.class)
.ifPresent(x -> v.get().type(x));
// Replace with bean from bean store.
beanStore
.getBean(RestMatcherList.class)
.ifPresent(x->v.get().impl(x));
// Replace with builder from: public [static] RestMatcherList.Builder createMatchers(<args>)
beanStore
.createMethodFinder(RestMatcherList.Builder.class)
.addBean(RestMatcherList.Builder.class, v.get())
.find("createMatchers")
.run(x -> v.set(x));
// Replace with bean from: public [static] RestMatcherList createMatchers(<args>)
beanStore
.createMethodFinder(RestMatcherList.class)
.addBean(RestMatcherList.Builder.class, v.get())
.find("createMatchers")
.run(x -> v.get().impl(x));
return v.get();
}
final RestMatcherList getMatchers(RestContext restContext) {
RestMatcherList.Builder b = matchers();
if (clientVersion != null)
b.append(new ClientVersionMatcher(restContext.getClientVersionHeader(), MethodInfo.of(restMethod)));
return b.build();
}
//-----------------------------------------------------------------------------------------------------------------
// pathMatchers
//-----------------------------------------------------------------------------------------------------------------
/**
* Instantiates the path matchers for this method.
*
* @return The path matchers for this method.
*/
protected UrlPathMatcherList getPathMatchers() {
Value<UrlPathMatcherList> v = Value.of(
UrlPathMatcherList.create()
);
if (path != null) {
for (String p : path) {
if (dotAll && ! p.endsWith("/*"))
p += "/*";
v.get().add(UrlPathMatcher.of(p));
}
}
if (v.get().isEmpty()) {
MethodInfo mi = MethodInfo.of(restMethod);
String p = null;
String httpMethod = null;
if (mi.hasAnnotation(RestGet.class))
httpMethod = "get";
else if (mi.hasAnnotation(RestPut.class))
httpMethod = "put";
else if (mi.hasAnnotation(RestPost.class))
httpMethod = "post";
else if (mi.hasAnnotation(RestDelete.class))
httpMethod = "delete";
else if (mi.hasAnnotation(RestOp.class)) {
Value<String> _httpMethod = Value.empty();
mi.forEachAnnotation(RestOp.class, x -> isNotEmpty(x.method()), x -> _httpMethod.set(x.method()));
httpMethod = _httpMethod.orElse(null);
}
p = HttpUtils.detectHttpPath(restMethod, httpMethod);
if (dotAll && ! p.endsWith("/*"))
p += "/*";
v.get().add(UrlPathMatcher.of(p));
}
beanStore
.createMethodFinder(UrlPathMatcherList.class, resource().get())
.addBean(UrlPathMatcherList.class, v.get())
.find("createPathMatchers", Method.class)
.run(x -> v.set(x));
return v.get();
}
/**
* When enabled, append <js>"/*"</js> to path patterns if not already present.
*
* @return This object.
*/
public Builder dotAll() {
dotAll = true;
return this;
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestHeaders
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request headers sub-builder.
*
* @return The default request headers sub-builder.
*/
public final HeaderList.Builder defaultRequestHeaders() {
if (defaultRequestHeaders == null)
defaultRequestHeaders = createDefaultRequestHeaders(beanStore(), parent, resource());
return defaultRequestHeaders;
}
/**
* Applies an operation to the default request headers sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .defaultRequestHeaders(<jv>x</jv> -&gt; <jv>x</jv>.remove(<js>"Foo"</js>)))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder defaultRequestHeaders(Consumer<HeaderList.Builder> operation) {
operation.accept(defaultRequestHeaders());
return this;
}
/**
* Instantiates the default request headers sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new default request headers sub-builder.
*/
protected HeaderList.Builder createDefaultRequestHeaders(BeanStore beanStore, RestContext.Builder parent, Supplier<?> resource) {
Value<HeaderList.Builder> v = Value.of(
parent.defaultRequestHeaders().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(HeaderList.Builder.class, v.get())
.createMethodFinder(HeaderList.Builder.class, resource)
.find("createDefaultRequestHeaders", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(HeaderList.Builder.class, v.get())
.createMethodFinder(HeaderList.class, resource)
.find("createDefaultRequestHeaders", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// defaultResponseHeaders
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default response headers sub-builder.
*
* @return The default response headers sub-builder.
*/
public final HeaderList.Builder defaultResponseHeaders() {
if (defaultResponseHeaders == null)
defaultResponseHeaders = createDefaultResponseHeaders(beanStore(), parent, resource());
return defaultResponseHeaders;
}
/**
* Applies an operation to the default response headers sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .defaultResponseHeaders(<jv>x</jv> -&gt; <jv>x</jv>.remove(<js>"Foo"</js>)))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder defaultResponseHeaders(Consumer<HeaderList.Builder> operation) {
operation.accept(defaultResponseHeaders());
return this;
}
/**
* Instantiates the default response headers sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new default response headers sub-builder.
*/
protected HeaderList.Builder createDefaultResponseHeaders(BeanStore beanStore, RestContext.Builder parent, Supplier<?> resource) {
Value<HeaderList.Builder> v = Value.of(
parent.defaultResponseHeaders().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(HeaderList.Builder.class, v.get())
.createMethodFinder(HeaderList.Builder.class, resource)
.find("createDefaultResponseHeaders", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(HeaderList.Builder.class, v.get())
.createMethodFinder(HeaderList.class, resource)
.find("createDefaultResponseHeaders", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestAttributes
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request attributes sub-builder.
*
* @return The default request attributes sub-builder.
*/
public final NamedAttributeList.Builder defaultRequestAttributes() {
if (defaultRequestAttributes == null)
defaultRequestAttributes = createDefaultRequestAttributes(beanStore(), parent, resource());
return defaultRequestAttributes;
}
/**
* Applies an operation to the default request attributes sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .defaultRequestAttributes(<jv>x</jv> -&gt; <jv>x</jv>.add(BasicNamedAttribute.<jsm>of</jsm>(<js>"Foo"</js>, ()-&gt;<jsm>getFoo</jsm>()))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder defaultRequestAttributes(Consumer<NamedAttributeList.Builder> operation) {
operation.accept(defaultRequestAttributes());
return this;
}
/**
* Instantiates the default request attributes sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new default request attributes sub-builder.
*/
protected NamedAttributeList.Builder createDefaultRequestAttributes(BeanStore beanStore, RestContext.Builder parent, Supplier<?> resource) {
Value<NamedAttributeList.Builder> v = Value.of(
parent.defaultRequestAttributes().copy()
);
BeanStore
.of(beanStore, resource)
.addBean(NamedAttributeList.Builder.class, v.get())
.createMethodFinder(NamedAttributeList.Builder.class, resource)
.find("createDefaultRequestAttributes", Method.class)
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(NamedAttributeList.Builder.class, v.get())
.createMethodFinder(NamedAttributeList.class, resource)
.find("createDefaultRequestAttributes", Method.class)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestQuery
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request query data sub-builder.
*
* @return The default request query data sub-builder.
*/
public final PartList.Builder defaultRequestQueryData() {
if (defaultRequestQueryData == null)
defaultRequestQueryData = createDefaultRequestQueryData(beanStore(), parent, resource());
return defaultRequestQueryData;
}
/**
* Applies an operation to the default request query data sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .defaultRequestQueryData(<jv>x</jv> -&gt; <jv>x</jv>.add(BasicPart.<jsm>of</jsm>(<js>"foo"</js>, ()-&gt;<jsm>getFoo</jsm>()))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder defaultRequestQueryData(Consumer<PartList.Builder> operation) {
operation.accept(defaultRequestQueryData());
return this;
}
/**
* Instantiates the default request query data sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new default request query data sub-builder.
*/
protected PartList.Builder createDefaultRequestQueryData(BeanStore beanStore, RestContext.Builder parent, Supplier<?> resource) {
Value<PartList.Builder> v = Value.of(
PartList.create()
);
BeanStore
.of(beanStore, resource)
.addBean(PartList.Builder.class, v.get())
.createMethodFinder(PartList.Builder.class, resource)
.find("createDefaultRequestQueryData", Method.class)
.thenFind("createDefaultRequestQueryData")
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(PartList.Builder.class, v.get())
.createMethodFinder(PartList.class, resource)
.find("createDefaultRequestQueryData", Method.class)
.thenFind("createDefaultRequestQueryData")
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestFormData
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request form data sub-builder.
*
* @return The default request form data sub-builder.
*/
public final PartList.Builder defaultRequestFormData() {
if (defaultRequestFormData == null)
defaultRequestFormData = createDefaultRequestFormData(beanStore(), parent, resource());
return defaultRequestFormData;
}
/**
* Applies an operation to the default request form data sub-builder.
*
* <p>
* Typically used to allow you to execute operations without breaking the fluent flow of the context builder.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* RestOpContext <jv>context</jv> = RestOpContext
* .<jsm>create</jsm>(<jv>method</jv>, <jv>restContext</jv>)
* .defaultRequestFormData(<jv>x</jv> -&gt; <jv>x</jv>.add(BasicPart.<jsm>of</jsm>(<js>"foo"</js>, ()-&gt;<jsm>getFoo</jsm>()))
* .build();
* </p>
*
* @param operation The operation to apply.
* @return This object.
*/
public final Builder defaultRequestFormData(Consumer<PartList.Builder> operation) {
operation.accept(defaultRequestFormData());
return this;
}
/**
* Instantiates the default request form data sub-builder.
*
* @param beanStore
* The factory used for creating beans and retrieving injected beans.
* @param parent
* The builder for the REST resource class.
* @param resource
* The REST servlet/bean instance that this context is defined against.
* @return A new default request form data sub-builder.
*/
protected PartList.Builder createDefaultRequestFormData(BeanStore beanStore, RestContext.Builder parent, Supplier<?> resource) {
Value<PartList.Builder> v = Value.of(
PartList.create()
);
BeanStore
.of(beanStore, resource)
.addBean(PartList.Builder.class, v.get())
.createMethodFinder(PartList.Builder.class, resource)
.find("createDefaultRequestFormData", Method.class)
.thenFind("createDefaultRequestFormData")
.run(x -> v.set(x));
BeanStore
.of(beanStore, resource)
.addBean(PartList.Builder.class, v.get())
.createMethodFinder(PartList.class, resource)
.find("createDefaultRequestFormData", Method.class)
.thenFind("createDefaultRequestFormData")
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// Parameter annotations
//-----------------------------------------------------------------------------------------------------------------
/**
* Handles processing of any annotations on parameters.
*
* <p>
* This includes: {@link Header}, {@link Query}, {@link FormData}.
*/
protected void processParameterAnnotations() {
for (Annotation[] aa : restMethod.getParameterAnnotations()) {
String def = null;
for (Annotation a : aa) {
if (a instanceof Schema) {
Schema s = (Schema)a;
def = joinnlFirstNonEmptyArray(s._default(), s.df());
}
}
for (Annotation a : aa) {
if (a instanceof Header) {
Header h = (Header)a;
if (def != null) {
try {
defaultRequestHeaders().set(basicHeader(firstNonEmpty(h.name(), h.value()), parseAnything(def)));
} catch (ParseException e) {
throw new ConfigException(e, "Malformed @Header annotation");
}
}
}
if (a instanceof Query) {
Query h = (Query)a;
if (def != null) {
try {
defaultRequestQueryData().setDefault(basicPart(firstNonEmpty(h.name(), h.value()), parseAnything(def)));
} catch (ParseException e) {
throw new ConfigException(e, "Malformed @Query annotation");
}
}
}
if (a instanceof FormData) {
FormData h = (FormData)a;
if (def != null) {
try {
defaultRequestFormData().setDefault(basicPart(firstNonEmpty(h.name(), h.value()), parseAnything(def)));
} catch (ParseException e) {
throw new ConfigException(e, "Malformed @FormData annotation");
}
}
}
}
}
}
//----------------------------------------------------------------------------------------------------
// Properties
//----------------------------------------------------------------------------------------------------
/**
* Specifies a {@link BeanStore} to use when resolving constructor arguments.
*
* @param beanStore The bean store to use for resolving constructor arguments.
* @return This object.
*/
public Builder beanStore(BeanStore beanStore) {
this.beanStore = beanStore;
return this;
}
/**
* Client version pattern matcher.
*
* <p>
* Specifies whether this method can be called based on the client version.
*
* <p>
* The client version is identified via the HTTP request header identified by
* {@link Rest#clientVersionHeader() @Rest(clientVersionHeader)} which by default is <js>"Client-Version"</js>.
*
* <p>
* This is a specialized kind of {@link RestMatcher} that allows you to invoke different Java methods for the same
* method/path based on the client version.
*
* <p>
* The format of the client version range is similar to that of OSGi versions.
*
* <p>
* In the following example, the Java methods are mapped to the same HTTP method and URL <js>"/foobar"</js>.
* <p class='bjava'>
* <jc>// Call this method if Client-Version is at least 2.0.
* // Note that this also matches 2.0.1.</jc>
* <ja>@RestGet</ja>(path=<js>"/foobar"</js>, clientVersion=<js>"2.0"</js>)
* <jk>public</jk> Object method1() {...}
*
* <jc>// Call this method if Client-Version is at least 1.1, but less than 2.0.</jc>
* <ja>@RestGet</ja>(path=<js>"/foobar"</js>, clientVersion=<js>"[1.1,2.0)"</js>)
* <jk>public</jk> Object method2() {...}
*
* <jc>// Call this method if Client-Version is less than 1.1.</jc>
* <ja>@RestGet</ja>(path=<js>"/foobar"</js>, clientVersion=<js>"[0,1.1)"</js>)
* <jk>public</jk> Object method3() {...}
* </p>
*
* <p>
* It's common to combine the client version with transforms that will convert new POJOs into older POJOs for
* backwards compatibility.
* <p class='bjava'>
* <jc>// Call this method if Client-Version is at least 2.0.</jc>
* <ja>@RestGet</ja>(path=<js>"/foobar"</js>, clientVersion=<js>"2.0"</js>)
* <jk>public</jk> NewPojo newMethod() {...}
*
* <jc>// Call this method if Client-Version is at least 1.1, but less than 2.0.</jc>
* <ja>@RestGet</ja>(path=<js>"/foobar"</js>, clientVersion=<js>"[1.1,2.0)"</js>)
* <ja>@BeanConfig(swaps=NewToOldSwap.<jk>class</jk>)
* <jk>public</jk> NewPojo oldMethod() {
* <jk>return</jk> newMethod();
* }
*
* <p>
* Note that in the previous example, we're returning the exact same POJO, but using a transform to convert it into
* an older form.
* The old method could also just return back a completely different object.
* The range can be any of the following:
* <ul>
* <li><js>"[0,1.0)"</js> = Less than 1.0. 1.0 and 1.0.0 does not match.
* <li><js>"[0,1.0]"</js> = Less than or equal to 1.0. Note that 1.0.1 will match.
* <li><js>"1.0"</js> = At least 1.0. 1.0 and 2.0 will match.
* </ul>
*
* <ul class='seealso'>
* <li class='ja'>{@link RestOp#clientVersion}
* <li class='ja'>{@link RestGet#clientVersion}
* <li class='ja'>{@link RestPut#clientVersion}
* <li class='ja'>{@link RestPost#clientVersion}
* <li class='ja'>{@link RestDelete#clientVersion}
* <li class='jm'>{@link RestContext.Builder#clientVersionHeader(String)}
* </ul>
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder clientVersion(String value) {
clientVersion = value;
return this;
}
/**
* Debug mode.
*
* <p>
* Enables the following:
* <ul class='spaced-list'>
* <li>
* HTTP request/response bodies are cached in memory for logging purposes.
* </ul>
*
* <p>
* If not sppecified, the debug enablement is inherited from the class context.
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder debug(Enablement value) {
debug = value;
return this;
}
/**
* Default character encoding.
*
* <p>
* The default character encoding for the request and response if not specified on the request.
*
* <p>
* This overrides the value defined on the {@link RestContext}.
*
* <ul class='seealso'>
* <li class='jm'>{@link RestContext.Builder#defaultCharset(Charset)}
* <li class='ja'>{@link Rest#defaultCharset}
* <li class='ja'>{@link RestOp#defaultCharset}
* </ul>
*
* @param value
* The new value for this setting.
* <br>The default is the first value found:
* <ul>
* <li>System property <js>"RestContext.defaultCharset"
* <li>Environment variable <js>"RESTCONTEXT_defaultCharset"
* <li><js>"utf-8"</js>
* </ul>
* @return This object.
*/
@FluentSetter
public Builder defaultCharset(Charset value) {
defaultCharset = value;
return this;
}
/**
* HTTP method name.
*
* <p>
* Typically <js>"GET"</js>, <js>"PUT"</js>, <js>"POST"</js>, <js>"DELETE"</js>, or <js>"OPTIONS"</js>.
*
* <p>
* Method names are case-insensitive (always folded to upper-case).
*
* <p>
* Note that you can use {@link org.apache.juneau.http.HttpMethod} for constant values.
*
* <p>
* Besides the standard HTTP method names, the following can also be specified:
* <ul class='spaced-list'>
* <li>
* <js>"*"</js>
* - Denotes any method.
* <br>Use this if you want to capture any HTTP methods in a single Java method.
* <br>The {@link org.apache.juneau.rest.annotation.Method @Method} annotation and/or {@link RestRequest#getMethod()} method can be used to
* distinguish the actual HTTP method name.
* <li>
* <js>""</js>
* - Auto-detect.
* <br>The method name is determined based on the Java method name.
* <br>For example, if the method is <c>doPost(...)</c>, then the method name is automatically detected
* as <js>"POST"</js>.
* <br>Otherwise, defaults to <js>"GET"</js>.
* <li>
* <js>"RRPC"</js>
* - Remote-proxy interface.
* <br>This denotes a Java method that returns an object (usually an interface, often annotated with the
* {@link Remote @Remote} annotation) to be used as a remote proxy using
* <c>RestClient.getRemoteInterface(Class&lt;T&gt; interfaceClass, String url)</c>.
* <br>This allows you to construct client-side interface proxies using REST as a transport medium.
* <br>Conceptually, this is simply a fancy <c>POST</c> against the url <js>"/{path}/{javaMethodName}"</js>
* where the arguments are marshalled from the client to the server as an HTTP content containing an array of
* objects, passed to the method as arguments, and then the resulting object is marshalled back to the client.
* <li>
* Anything else
* - Overloaded non-HTTP-standard names that are passed in through a <c>&amp;method=methodName</c> URL
* parameter.
* </ul>
*
* <ul class='seealso'>
* <li class='ja'>{@link RestOp#method()}
* <li class='ja'>{@link RestGet}
* <li class='ja'>{@link RestPut}
* <li class='ja'>{@link RestPost}
* <li class='ja'>{@link RestDelete}
* </ul>
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder httpMethod(String value) {
this.httpMethod = value;
return this;
}
/**
* The maximum allowed input size (in bytes) on HTTP requests.
*
* <p>
* Useful for alleviating DoS attacks by throwing an exception when too much input is received instead of resulting
* in out-of-memory errors which could affect system stability.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* <jc>// Option #1 - Defined via annotation resolving to a config file setting with default value.</jc>
* <ja>@Rest</ja>(maxInput=<js>"$C{REST/maxInput,10M}"</js>)
* <jk>public class</jk> MyResource {
*
* <jc>// Option #2 - Defined via builder passed in through resource constructor.</jc>
* <jk>public</jk> MyResource(RestContext.Builder <jv>builder</jv>) <jk>throws</jk> Exception {
*
* <jc>// Using method on builder.</jc>
* <jv>builder</jv>.maxInput(<js>"10M"</js>);
* }
*
* <jc>// Option #3 - Defined via builder passed in through init method.</jc>
* <ja>@RestHook</ja>(<jsf>INIT</jsf>)
* <jk>public void</jk> init(RestContext.Builder <jv>builder</jv>) <jk>throws</jk> Exception {
* <jv>builder</jv>.maxInput(<js>"10M"</js>);
* }
*
* <jc>// Override at the method level.</jc>
* <ja>@RestPost</ja>(maxInput=<js>"10M"</js>)
* <jk>public</jk> Object myMethod() {...}
* }
* </p>
*
* <ul class='notes'>
* <li class='note'>
* String value that gets resolved to a <jk>long</jk>.
* <li class='note'>
* Can be suffixed with any of the following representing kilobytes, megabytes, and gigabytes:
* <js>'K'</js>, <js>'M'</js>, <js>'G'</js>.
* <li class='note'>
* A value of <js>"-1"</js> can be used to represent no limit.
* </ul>
*
* <ul class='seealso'>
* <li class='ja'>{@link Rest#maxInput}
* <li class='ja'>{@link RestOp#maxInput}
* <li class='jm'>{@link RestOpContext.Builder#maxInput(String)}
* </ul>
*
* @param value
* The new value for this setting.
* <br>The default is the first value found:
* <ul>
* <li>System property <js>"RestContext.maxInput"
* <li>Environment variable <js>"RESTCONTEXT_MAXINPUT"
* <li><js>"100M"</js>
* </ul>
* <br>The default is <js>"100M"</js>.
* @return This object.
*/
@FluentSetter
public Builder maxInput(String value) {
maxInput = StringUtils.parseLongWithSuffix(value);
return this;
}
/**
* Resource method paths.
*
* <p>
* Identifies the URL subpath relative to the servlet class.
*
* <p>
* <ul class='notes'>
* <li class='note'>
* This method is only applicable for Java methods.
* <li class='note'>
* Slashes are trimmed from the path ends.
* <br>As a convention, you may want to start your path with <js>'/'</js> simple because it make it easier to read.
* </ul>
*
* @param values The new values for this setting.
* @return This object.
*/
@FluentSetter
public Builder path(String...values) {
path = prependAll(path, values);
return this;
}
/**
* Supported accept media types.
*
* <p>
* Overrides the media types inferred from the serializers that identify what media types can be produced by the resource.
* <br>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.
*
* <p>
* This affects the returned values from the following:
* <ul class='javatree'>
* <li class='jm'>{@link RestContext#getProduces() RestContext.getProduces()}
* <li class='jm'>{@link SwaggerProvider#getSwagger(RestContext,Locale)} - Affects produces field.
* </ul>
*
* <ul class='seealso'>
* <li class='ja'>{@link Rest#produces}
* <li class='ja'>{@link RestOp#produces}
* <li class='ja'>{@link RestGet#produces}
* <li class='ja'>{@link RestPut#produces}
* <li class='ja'>{@link RestPost#produces}
* </ul>
*
* @param values The values to add to this setting.
* @return This object.
*/
@FluentSetter
public Builder produces(MediaType...values) {
produces = addAll(produces, values);
return this;
}
/**
* Declared roles.
*
* <p>
* A comma-delimited list of all possible user roles.
*
* <p>
* Used in conjunction with {@link RestOpContext.Builder#roleGuard(String)} is used with patterns.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* <ja>@Rest</ja>(
* rolesDeclared=<js>"ROLE_ADMIN,ROLE_READ_WRITE,ROLE_READ_ONLY,ROLE_SPECIAL"</js>,
* roleGuard=<js>"ROLE_ADMIN || (ROLE_READ_WRITE &amp;&amp; ROLE_SPECIAL)"</js>
* )
* <jk>public class</jk> MyResource <jk>extends</jk> BasicRestServlet <jk>implements</jk> BasicUniversalConfig {
* ...
* }
* </p>
*
* <ul class='seealso'>
* <li class='ja'>{@link Rest#rolesDeclared}
* </ul>
*
* @param values The values to add to this setting.
* @return This object.
*/
@FluentSetter
public Builder rolesDeclared(String...values) {
rolesDeclared = addAll(rolesDeclared, values);
return this;
}
/**
* Role guard.
*
* <p>
* An expression defining if a user with the specified roles are allowed to access methods on this class.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
* <ja>@Rest</ja>(
* path=<js>"/foo"</js>,
* roleGuard=<js>"ROLE_ADMIN || (ROLE_READ_WRITE &amp;&amp; ROLE_SPECIAL)"</js>
* )
* <jk>public class</jk> MyResource <jk>extends</jk> BasicRestServlet <jk>implements</jk> BasicUniversalConfig {
* ...
* }
* </p>
*
* <ul class='notes'>
* <li class='note'>
* Supports any of the following expression constructs:
* <ul>
* <li><js>"foo"</js> - Single arguments.
* <li><js>"foo,bar,baz"</js> - Multiple OR'ed arguments.
* <li><js>"foo | bar | bqz"</js> - Multiple OR'ed arguments, pipe syntax.
* <li><js>"foo || bar || bqz"</js> - Multiple OR'ed arguments, Java-OR syntax.
* <li><js>"fo*"</js> - Patterns including <js>'*'</js> and <js>'?'</js>.
* <li><js>"fo* &amp; *oo"</js> - Multiple AND'ed arguments, ampersand syntax.
* <li><js>"fo* &amp;&amp; *oo"</js> - Multiple AND'ed arguments, Java-AND syntax.
* <li><js>"fo* || (*oo || bar)"</js> - Parenthesis.
* </ul>
* <li class='note'>
* AND operations take precedence over OR operations (as expected).
* <li class='note'>
* Whitespace is ignored.
* <li class='note'>
* <jk>null</jk> or empty expressions always match as <jk>false</jk>.
* <li class='note'>
* If patterns are used, you must specify the list of declared roles using {@link Rest#rolesDeclared()} or {@link RestOpContext.Builder#rolesDeclared(String...)}.
* <li class='note'>
* Supports {@doc jrs.SvlVariables}
* (e.g. <js>"$L{my.localized.variable}"</js>).
* </ul>
*
* @param value The values to add to this setting.
* @return This object.
*/
@FluentSetter
public Builder roleGuard(String value) {
if (roleGuard == null)
roleGuard = set(value);
else
roleGuard.add(value);
return this;
}
/**
* Supported content media types.
*
* <p>
* Overrides the media types inferred from the parsers that identify what media types can be consumed by the resource.
* <br>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.
*
* <p>
* This affects the returned values from the following:
* <ul class='javatree'>
* <li class='jm'>{@link RestContext#getConsumes() RestContext.getConsumes()}
* </ul>
*
* <ul class='seealso'>
* <li class='ja'>{@link Rest#consumes}
* <li class='ja'>{@link RestOp#consumes}
* <li class='ja'>{@link RestPut#consumes}
* <li class='ja'>{@link RestPost#consumes}
* </ul>
*
* @param values The values to add to this setting.
* @return This object.
*/
@FluentSetter
public Builder consumes(MediaType...values) {
consumes = addAll(consumes, values);
return this;
}
// <FluentSetters>
@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<HashKey,? extends org.apache.juneau.Context> 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;
}
// </FluentSetters>
//-----------------------------------------------------------------------------------------------------------------
// Helper methods.
//-----------------------------------------------------------------------------------------------------------------
private String joinnlFirstNonEmptyArray(String[]...s) {
for (String[] ss : s)
if (ss.length > 0)
return joinnl(ss);
return null;
}
}
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
private final String httpMethod;
private final UrlPathMatcher[] pathMatchers;
private final RestGuard[] guards;
private final RestMatcher[] requiredMatchers, optionalMatchers;
private final RestConverter[] converters;
private final RestContext context;
private final Method method;
private final RestOpInvoker methodInvoker;
private final RestOpInvoker[]
preCallMethods,
postCallMethods;
private final MethodInfo mi;
private final BeanContext beanContext;
private final SerializerSet serializers;
private final ParserSet parsers;
private final EncoderSet encoders;
private final HttpPartSerializer partSerializer;
private final HttpPartParser partParser;
private final JsonSchemaGenerator jsonSchemaGenerator;
private final HeaderList defaultRequestHeaders, defaultResponseHeaders;
private final PartList defaultRequestQueryData, defaultRequestFormData;
private final NamedAttributeList defaultRequestAttributes;
private final Charset defaultCharset;
private final long maxInput;
private final List<MediaType>
supportedAcceptTypes,
supportedContentTypes;
private final RestLogger callLogger;
private final Map<Class<?>,ResponseBeanMeta> responseBeanMetas = new ConcurrentHashMap<>();
private final Map<Class<?>,ResponsePartMeta> headerPartMetas = new ConcurrentHashMap<>();
private final ResponseBeanMeta responseMeta;
private final int hierarchyDepth;
private final DebugEnablement debug;
/**
* Context constructor.
*
* @param builder The builder for this object.
* @throws ServletException If context could not be created.
*/
protected RestOpContext(Builder builder) throws ServletException {
super(builder);
try {
context = builder.restContext;
method = builder.restMethod;
if (builder.debug == null)
debug = context.getDebugEnablement();
else
debug = DebugEnablement.create(context.getRootBeanStore()).enable(builder.debug, "*").build();
mi = MethodInfo.of(method).accessible();
Object r = context.getResource();
BeanStore bs = BeanStore.of(context.getRootBeanStore(), r)
.addBean(RestOpContext.class, this)
.addBean(Method.class, method)
.addBean(AnnotationWorkList.class, builder.getApplied());
bs.addBean(BeanStore.class, bs);
beanContext = bs.add(BeanContext.class, builder.getBeanContext().orElse(context.getBeanContext()));
encoders = bs.add(EncoderSet.class, builder.getEncoders().orElse(context.getEncoders()));
serializers = bs.add(SerializerSet.class, builder.getSerializers().orElse(context.getSerializers()));
parsers = bs.add(ParserSet.class, builder.getParsers().orElse(context.getParsers()));
partSerializer = bs.add(HttpPartSerializer.class, builder.getPartSerializer().orElse(context.getPartSerializer()));
partParser = bs.add(HttpPartParser.class, builder.getPartParser().orElse(context.getPartParser()));
jsonSchemaGenerator = bs.add(JsonSchemaGenerator.class, builder.getJsonSchemaGenerator().orElse(context.getJsonSchemaGenerator()));
converters = bs.add(RestConverter[].class, builder.converters().build().asArray());
guards = bs.add(RestGuard[].class, builder.getGuards().asArray());
RestMatcherList matchers = builder.getMatchers(context);
optionalMatchers = matchers.getOptionalEntries();
requiredMatchers = matchers.getRequiredEntries();
pathMatchers = bs.add(UrlPathMatcher[].class, builder.getPathMatchers().asArray());
bs.addBean(UrlPathMatcher.class, pathMatchers.length > 0 ? pathMatchers[0] : null);
supportedAcceptTypes = unmodifiable(builder.produces != null ? builder.produces : serializers.getSupportedMediaTypes());
supportedContentTypes = unmodifiable(builder.consumes != null ? builder.consumes : parsers.getSupportedMediaTypes());
defaultRequestHeaders = builder.defaultRequestHeaders().build();
defaultResponseHeaders = builder.defaultResponseHeaders().build();
defaultRequestQueryData = builder.defaultRequestQueryData().build();
defaultRequestFormData = builder.defaultRequestFormData().build();
defaultRequestAttributes = builder.defaultRequestAttributes().build();
int _hierarchyDepth = 0;
Class<?> sc = method.getDeclaringClass().getSuperclass();
while (sc != null) {
_hierarchyDepth++;
sc = sc.getSuperclass();
}
hierarchyDepth = _hierarchyDepth;
String _httpMethod = builder.httpMethod;
if (_httpMethod == null)
_httpMethod = HttpUtils.detectHttpMethod(method, true, "GET");
if ("METHOD".equals(_httpMethod))
_httpMethod = "*";
httpMethod = _httpMethod.toUpperCase(Locale.ENGLISH);
defaultCharset = builder.defaultCharset != null ? builder.defaultCharset : context.defaultCharset;
maxInput = builder.maxInput != null ? builder.maxInput : context.maxInput;
responseMeta = ResponseBeanMeta.create(mi, builder.getApplied());
preCallMethods = context.getPreCallMethods().stream().map(x -> new RestOpInvoker(x, context.findRestOperationArgs(x, bs), context.getMethodExecStats(x))).toArray(RestOpInvoker[]::new);
postCallMethods = context.getPostCallMethods().stream().map(x -> new RestOpInvoker(x, context.findRestOperationArgs(x, bs), context.getMethodExecStats(x))).toArray(RestOpInvoker[]::new);
methodInvoker = new RestOpInvoker(method, context.findRestOperationArgs(method, bs), context.getMethodExecStats(method));
this.callLogger = context.getCallLogger();
} catch (Exception e) {
throw new ServletException(e);
}
}
/**
* Creates a new REST operation session.
*
* @param session The REST session.
* @return A new REST operation session.
* @throws Exception If op session could not be created.
*/
public RestOpSession.Builder createSession(RestSession session) throws Exception {
return RestOpSession.create(this, session).logger(callLogger).debug(debug.isDebug(this, session.getRequest()));
}
/**
* Creates a {@link RestRequest} object based on the specified incoming {@link HttpServletRequest} object.
*
* @param session The current REST call.
* @return The wrapped request object.
* @throws Exception If any errors occur trying to interpret the request.
*/
public RestRequest createRequest(RestSession session) throws Exception {
return new RestRequest(this, session);
}
/**
* Creates a {@link RestResponse} object based on the specified incoming {@link HttpServletResponse} object
* and the request returned by {@link #createRequest(RestSession)}.
*
* @param session The current REST call.
* @param req The REST request.
* @return The wrapped response object.
* @throws Exception If any errors occur trying to interpret the request or response.
*/
public RestResponse createResponse(RestSession session, RestRequest req) throws Exception {
return new RestResponse(this, session, req);
}
/**
* Returns the bean context associated with this context.
*
* @return The bean context associated with this context.
*/
public BeanContext getBeanContext() {
return beanContext;
}
/**
* Returns metadata about the specified response object if it's annotated with {@link Response @Response}.
*
* @param o The response POJO.
* @return Metadata about the specified response object, or <jk>null</jk> if it's not annotated with {@link Response @Response}.
*/
public ResponseBeanMeta getResponseBeanMeta(Object o) {
if (o == null)
return null;
Class<?> c = o.getClass();
ResponseBeanMeta rbm = responseBeanMetas.get(c);
if (rbm == null) {
rbm = ResponseBeanMeta.create(c, AnnotationWorkList.create());
if (rbm == null)
rbm = ResponseBeanMeta.NULL;
responseBeanMetas.put(c, rbm);
}
if (rbm == ResponseBeanMeta.NULL)
return null;
return rbm;
}
/**
* Returns metadata about the specified response object if it's annotated with {@link Header @Header}.
*
* @param o The response POJO.
* @return Metadata about the specified response object, or <jk>null</jk> if it's not annotated with {@link Header @Header}.
*/
public ResponsePartMeta getResponseHeaderMeta(Object o) {
if (o == null)
return null;
Class<?> c = o.getClass();
ResponsePartMeta pm = headerPartMetas.get(c);
if (pm == null) {
Header a = c.getAnnotation(Header.class);
if (a != null) {
HttpPartSchema schema = HttpPartSchema.create(a);
HttpPartSerializer serializer = createPartSerializer(schema.getSerializer(), partSerializer);
pm = new ResponsePartMeta(HEADER, schema, serializer);
}
if (pm == null)
pm = ResponsePartMeta.NULL;
headerPartMetas.put(c, pm);
}
if (pm == ResponsePartMeta.NULL)
return null;
return pm;
}
/**
* Returns the HTTP method name (e.g. <js>"GET"</js>).
*
* @return The HTTP method name.
*/
public String getHttpMethod() {
return httpMethod;
}
/**
* Returns the path pattern for this method.
*
* @return The path pattern.
*/
public String getPathPattern() {
return pathMatchers[0].toString();
}
/**
* Returns the serializers to use for this method.
*
* @return The serializers to use for this method.
*/
public SerializerSet getSerializers() {
return serializers;
}
/**
* Returns the parsers to use for this method.
*
* @return The parsers to use for this method.
*/
public ParserSet getParsers() {
return parsers;
}
/**
* Returns the compression encoders to use for this method.
*
* @return The compression encoders to use for this method.
*/
public EncoderSet getEncoders() {
return encoders;
}
/**
* Bean property getter: <property>partSerializer</property>.
*
* @return The value of the <property>partSerializer</property> property on this bean, or <jk>null</jk> if it is not set.
*/
public HttpPartSerializer getPartSerializer() {
return partSerializer;
}
/**
* Bean property getter: <property>partParser</property>.
*
* @return The value of the <property>partParser</property> property on this bean, or <jk>null</jk> if it is not set.
*/
public HttpPartParser getPartParser() {
return partParser;
}
/**
* Returns the JSON-Schema generator applicable to this Java method.
*
* @return The JSON-Schema generator applicable to this Java method.
*/
public JsonSchemaGenerator getJsonSchemaGenerator() {
return jsonSchemaGenerator;
}
/**
* Returns the underlying Java method that this context belongs to.
*
* @return The underlying Java method that this context belongs to.
*/
public Method getJavaMethod() {
return method;
}
/**
* Returns the default request headers.
*
* @return The default request headers. Never <jk>null</jk>.
*/
public HeaderList getDefaultRequestHeaders() {
return defaultRequestHeaders;
}
/**
* Returns the default response headers.
*
* @return The default response headers. Never <jk>null</jk>.
*/
public HeaderList getDefaultResponseHeaders() {
return defaultResponseHeaders;
}
/**
* Returns the default request query parameters.
*
* @return The default request query parameters. Never <jk>null</jk>.
*/
public PartList getDefaultRequestQueryData() {
return defaultRequestQueryData;
}
/**
* Returns the default form data parameters.
*
* @return The default form data parameters. Never <jk>null</jk>.
*/
public PartList getDefaultRequestFormData() {
return defaultRequestFormData;
}
/**
* Returns the default request attributes.
*
* @return The default request attributes. Never <jk>null</jk>.
*/
public NamedAttributeList getDefaultRequestAttributes() {
return defaultRequestAttributes;
}
/**
* Returns the default charset.
*
* @return The default charset. Never <jk>null</jk>.
*/
public Charset getDefaultCharset() {
return defaultCharset;
}
/**
* Returns the max number of bytes to process in the input content.
*
* @return The max number of bytes to process in the input content.
*/
public long getMaxInput() {
return maxInput;
}
/**
* Returns the list of supported content types.
*
* @return An unmodifiable list.
*/
public List<MediaType> getSupportedContentTypes() {
return supportedContentTypes;
}
/**
* Returns a list of supported accept types.
*
* @return An unmodifiable list.
*/
public List<MediaType> getSupportedAcceptTypes() {
return supportedAcceptTypes;
}
/**
* Returns the response bean meta if this method returns a {@link Response}-annotated bean.
*
* @return The response bean meta or <jk>null</jk> if it's not a {@link Response}-annotated bean.
*/
public ResponseBeanMeta getResponseMeta() {
return responseMeta;
}
/**
* Identifies if this method can process the specified call.
*
* <p>
* To process the call, the following must be true:
* <ul>
* <li>Path pattern must match.
* <li>Matchers (if any) must match.
* </ul>
*
* @param session The call to check.
* @return
* One of the following values:
* <ul>
* <li><c>0</c> - Path doesn't match.
* <li><c>1</c> - Path matched but matchers did not.
* <li><c>2</c> - Matches.
* </ul>
*/
protected int match(RestSession session) {
UrlPathMatch pm = matchPattern(session);
if (pm == null)
return 0;
if (requiredMatchers.length == 0 && optionalMatchers.length == 0) {
session.urlPathMatch(pm); // Cache so we don't have to recalculate.
return 2;
}
try {
HttpServletRequest req = session.getRequest();
// If the method implements matchers, test them.
for (RestMatcher m : requiredMatchers)
if (! m.matches(req))
return 1;
if (optionalMatchers.length > 0) {
boolean matches = false;
for (RestMatcher m : optionalMatchers)
matches |= m.matches(req);
if (! matches)
return 1;
}
session.urlPathMatch(pm); // Cache so we don't have to recalculate.
return 2;
} catch (Exception e) {
throw new InternalServerError(e);
}
}
RestOpInvoker getMethodInvoker() {
return methodInvoker;
}
RestGuard[] getGuards() {
return guards;
}
RestConverter[] getConverters() {
return converters;
}
RestOpInvoker[] getPreCallMethods() {
return preCallMethods;
}
RestOpInvoker[] getPostCallMethods() {
return postCallMethods;
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* Context */
public Context.Builder copy() {
throw new UnsupportedOperationException("Method not implemented.");
}
/*
* compareTo() method is used to keep SimpleMethods ordered in the RestCallRouter list.
* It maintains the order in which matches are made during requests.
*/
@Override /* Comparable */
public int compareTo(RestOpContext o) {
int c;
for (int i = 0; i < Math.min(pathMatchers.length, o.pathMatchers.length); i++) {
c = pathMatchers[i].compareTo(o.pathMatchers[i]);
if (c != 0)
return c;
}
c = compare(o.hierarchyDepth, hierarchyDepth);
if (c != 0)
return c;
c = compare(o.requiredMatchers.length, requiredMatchers.length);
if (c != 0)
return c;
c = compare(o.optionalMatchers.length, optionalMatchers.length);
if (c != 0)
return c;
c = compare(o.guards.length, guards.length);
if (c != 0)
return c;
c = compare(method.getName(), o.method.getName());
if (c != 0)
return c;
c = compare(method.getParameterCount(), o.method.getParameterCount());
if (c != 0)
return c;
for (int i = 0; i < method.getParameterCount(); i++) {
c = compare(method.getParameterTypes()[i].getName(), o.method.getParameterTypes()[i].getName());
if (c != 0)
return c;
}
c = compare(method.getReturnType().getName(), o.method.getReturnType().getName());
if (c != 0)
return c;
return 0;
}
@Override /* Object */
public boolean equals(Object o) {
return (o instanceof RestOpContext) && eq(this, (RestOpContext)o, (x,y)->x.method.equals(y.method));
}
@Override /* Object */
public int hashCode() {
return method.hashCode();
}
@Override /* Context */
protected JsonMap properties() {
return filteredMap()
.append("defaultRequestFormData", defaultRequestFormData)
.append("defaultRequestHeaders", defaultRequestHeaders)
.append("defaultRequestQueryData", defaultRequestQueryData)
.append("httpMethod", httpMethod);
}
//-----------------------------------------------------------------------------------------------------------------
// Helper methods.
//-----------------------------------------------------------------------------------------------------------------
private static HttpPartSerializer createPartSerializer(Class<? extends HttpPartSerializer> c, HttpPartSerializer _default) {
return BeanCreator.of(HttpPartSerializer.class).type(c).orElse(_default);
}
private UrlPathMatch matchPattern(RestSession call) {
UrlPathMatch pm = null;
for (UrlPathMatcher pp : pathMatchers)
if (pm == null)
pm = pp.match(call.getUrlPath());
return pm;
}
}