![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.juneau.rest.RestOpContext 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 org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import static org.apache.juneau.collections.JsonMap.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.common.internal.ThrowableUtils.*;
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.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 jakarta.servlet.*;
import jakarta.servlet.http.*;
import org.apache.http.*;
import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.common.internal.*;
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.HttpPartSerializer.Creator;
import org.apache.juneau.httppart.bean.*;
import org.apache.juneau.internal.*;
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}.
*
* Notes:
* - This class is thread safe and reusable.
*
*
* See Also:
* - RestOpContext
*
*/
public class RestOpContext extends Context implements Comparable {
//-------------------------------------------------------------------------------------------------------------------
// Static
//-------------------------------------------------------------------------------------------------------------------
/**
* 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 final class Builder extends Context.Builder {
RestContext restContext;
RestContext.Builder parent;
Method restMethod;
String httpMethod, clientVersion;
Enablement debug;
List 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 defaultRequestFormData, defaultRequestQueryData;
NamedAttributeMap defaultRequestAttributes;
HeaderList defaultRequestHeaders, defaultResponseHeaders;
RestMatcherList.Builder restMatchers;
List produces, consumes;
Set 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 new InternalServerError(e);
}
}
/**
* 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.getBeanStore(), 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 new InternalServerError(e);
}
}
/**
* 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 restContext.builder.resource();
}
/**
* Returns the default classes 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 classes list for this builder.
*/
public DefaultClassList defaultClasses() {
return restContext.builder.defaultClasses();
}
//-----------------------------------------------------------------------------------------------------------------
// beanStore
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns access to the bean store being used by this builder.
*
*
* Can be used to add more beans to the bean store.
*
* @return The bean store being used by this builder.
*/
public BeanStore beanStore() {
return beanStore;
}
/**
* Specifies a {@link BeanStore} to use when resolving constructor arguments.
*
* @param beanStore The bean store to use for resolving constructor arguments.
* @return This object.
*/
protected Builder beanStore(BeanStore beanStore) {
this.beanStore = beanStore;
return this;
}
/**
* Adds a bean to the bean store of this operation.
*
*
* Equivalent to calling:
*
* builder .beanStore().add(beanType , bean );
*
*
* @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 operation.
*
*
* Equivalent to calling:
*
* builder .beanStore().add(beanType , bean , name );
*
*
* @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;
}
//-----------------------------------------------------------------------------------------------------------------
// beanContext
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the bean context sub-builder.
*
* @return The bean context sub-builder.
*/
public BeanContext.Builder beanContext() {
if (beanContext == null)
beanContext = createBeanContext(beanStore(), parent, resource());
return beanContext;
}
/**
* 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 v = Value.of(
parent.beanContext().copy()
);
// Replace with bean from: @RestInject(methodScope="foo") public [static] BeanContext xxx()
BeanStore
.of(beanStore, resource)
.addBean(BeanContext.Builder.class, v.get())
.createMethodFinder(BeanContext.class, resource)
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
Optional getBeanContext() {
return optional(beanContext).map(BeanContext.Builder::build);
}
//-----------------------------------------------------------------------------------------------------------------
// encoders
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the encoder group sub-builder.
*
* @return The encoder group sub-builder.
*/
public EncoderSet.Builder encoders() {
if (encoders == null)
encoders = createEncoders(beanStore(), parent, resource());
return encoders;
}
/**
* Adds one or more encoders to this operation.
*
*
* Equivalent to calling:
*
* builder .encoders().add(value );
*
*
* @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 operation.
*
*
* Equivalent to calling:
*
* builder .encoders().add(value );
*
*
* @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.
*
* @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 v = Value.of(
parent.encoders().copy()
);
// Replace with bean from: @RestInject(methodScope="foo") public [static] EncoderSet xxx()
BeanStore
.of(beanStore, resource)
.addBean(EncoderSet.Builder.class, v.get())
.createMethodFinder(EncoderSet.class, resource)
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
Optional getEncoders() {
return optional(encoders).map(EncoderSet.Builder::build);
}
//-----------------------------------------------------------------------------------------------------------------
// serializers
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the serializer group sub-builder.
*
* @return The serializer group sub-builder.
*/
public SerializerSet.Builder serializers() {
if (serializers == null)
serializers = createSerializers(beanStore(), parent, resource());
return serializers;
}
/**
* Adds one or more serializers to this operation.
*
*
* Equivalent to calling:
*
* builder .serializers().add(value );
*
*
* @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 operation.
*
*
* Equivalent to calling:
*
* builder .serializers().add(value );
*
*
* @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.
*
* @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 v = Value.of(
parent.serializers().copy()
);
// Replace with bean from: @RestInject(methodScope="foo") public [static] SerializerSet xxx()
BeanStore
.of(beanStore, resource)
.addBean(SerializerSet.Builder.class, v.get())
.createMethodFinder(SerializerSet.class, resource)
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
Optional getSerializers() {
return optional(serializers).map(SerializerSet.Builder::build);
}
//-----------------------------------------------------------------------------------------------------------------
// parsers
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the parser group sub-builder.
*
* @return The parser group sub-builder.
*/
public ParserSet.Builder parsers() {
if (parsers == null)
parsers = createParsers(beanStore(), parent, resource());
return parsers;
}
/**
* Adds one or more parsers to this operation.
*
*
* Equivalent to calling:
*
* builder .parsers().add(value );
*
*
* @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 operation.
*
*
* Equivalent to calling:
*
* builder .parsers().add(value );
*
*
* @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.
*
* @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 v = Value.of(
parent.parsers().copy()
);
// Replace with bean from: @RestInject(methodScope="foo") public [static] ParserSet xxx()
BeanStore
.of(beanStore, resource)
.addBean(ParserSet.Builder.class, v.get())
.createMethodFinder(ParserSet.class, resource)
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
Optional getParsers() {
return optional(parsers).map(ParserSet.Builder::build);
}
//-----------------------------------------------------------------------------------------------------------------
// partSerializer
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the part serializer sub-builder.
*
* @return The part serializer sub-builder.
*/
public HttpPartSerializer.Creator partSerializer() {
if (partSerializer == null)
partSerializer = createPartSerializer(beanStore(), parent, resource());
return partSerializer;
}
/**
* Specifies the part serializer to use for serializing HTTP parts for this operation.
*
*
* Equivalent to calling:
*
* builder .partSerializer().type(value );
*
*
* @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 operation.
*
*
* Equivalent to calling:
*
* builder .partSerializer().impl(value );
*
*
* @param value The new value.
* @return This object.
*/
public Builder partSerializer(HttpPartSerializer value) {
partSerializer().impl(value);
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 v = Value.of(
parent.partSerializer().copy()
);
// Replace with bean from: @RestInject(methodScope="foo") public [static] HttpPartSerializer xxx()
BeanStore
.of(beanStore, resource)
.addBean(HttpPartSerializer.Creator.class, v.get())
.createMethodFinder(HttpPartSerializer.class, resource)
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
Optional getPartSerializer() {
return optional(partSerializer).map(Creator::create);
}
//-----------------------------------------------------------------------------------------------------------------
// partParser
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the part parser sub-builder.
*
* @return The part parser sub-builder.
*/
public HttpPartParser.Creator partParser() {
if (partParser == null)
partParser = createPartParser(beanStore(), parent, resource());
return partParser;
}
/**
* Specifies the part parser to use for parsing HTTP parts for this operation.
*
*
* Equivalent to calling:
*
* builder .partParser().type(value );
*
*
* @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 operation.
*
*
* Equivalent to calling:
*
* builder .partParser().impl(value );
*
*
* @param value The new value.
* @return This object.
*/
public Builder partParser(HttpPartParser value) {
partParser().impl(value);
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 v = Value.of(
parent.partParser().copy()
);
// Replace with bean from: @RestInject(methodScope="foo") public [static] HttpPartParser xxx()
BeanStore
.of(beanStore, resource)
.addBean(HttpPartParser.Creator.class, v.get())
.createMethodFinder(HttpPartParser.class, resource)
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
Optional getPartParser() {
return optional(partParser).map(org.apache.juneau.httppart.HttpPartParser.Creator::create);
}
//-----------------------------------------------------------------------------------------------------------------
// jsonSchemaGenerator
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the JSON schema generator sub-builder.
*
* @return The JSON schema generator sub-builder.
*/
public JsonSchemaGenerator.Builder jsonSchemaGenerator() {
if (jsonSchemaGenerator == null)
jsonSchemaGenerator = createJsonSchemaGenerator(beanStore(), parent, resource());
return jsonSchemaGenerator;
}
/**
* Specifies the JSON schema generator for this operation.
*
*
* Equivalent to calling:
*
* builder .jsonSchemaGenerator().type(value );
*
*
* @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 operation.
*
*
* Equivalent to calling:
*
* builder .jsonSchemaGenerator().impl(value );
*
*
* @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.
*
* @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 v = Value.of(
parent.jsonSchemaGenerator().copy()
);
// Replace with bean from: @RestInject(methodScope="foo") public [static] JsonSchemaGenerator xxx()
BeanStore
.of(beanStore, resource)
.addBean(JsonSchemaGenerator.Builder.class, v.get())
.createMethodFinder(JsonSchemaGenerator.class, resource)
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
Optional getJsonSchemaGenerator() {
return optional(jsonSchemaGenerator).map(JsonSchemaGenerator.Builder::build);
}
//-----------------------------------------------------------------------------------------------------------------
// converters
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the response converter list sub-builder.
*
* @return The response converter list sub-builder.
*/
public RestConverterList.Builder converters() {
if (converters == null)
converters = createConverters(beanStore(), resource());
return converters;
}
/**
* Adds one or more converters to use to convert response objects for this operation.
*
*
* Equivalent to calling:
*
* builder .converters().append(value );
*
*
* @param value The new value.
* @return This object.
*/
@SafeVarargs
public final Builder converters(Class extends RestConverter>...value) {
converters().append(value);
return this;
}
/**
* Adds one or more converters to this operation.
*
*
* Equivalent to calling:
*
* builder .converters().append(value );
*
*
* @param value The new value.
* @return This object.
*/
public Builder converters(RestConverter...value) {
converters().append(value);
return this;
}
/**
* Instantiates the response converter list sub-builder.
*
*
* Associates one or more {@link RestConverter converters} with a resource class.
*
These converters get called immediately after execution of the REST method in the same order specified in the
* annotation.
*
The object passed into this converter is the object returned from the Java method or passed into
* the {@link RestResponse#setContent(Object)} method.
*
*
* Can be used for performing post-processing on the response object before serialization.
*
*
* 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).
*
*
Example:
*
* // Our converter.
* public class MyConverter implements RestConverter {
* @Override
* public Object convert(RestRequest req , Object object ) {
* // Do something with object and return another object.
* // Or just return the same object for a no-op.
* }
* }
*
* // Option #1 - Registered via annotation resolving to a config file setting with default value.
* @Rest (converters={MyConverter.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 .converters(MyConverter.class );
*
* // Pass in an instance instead.
* builder .converters(new MyConverter());
* }
*
* // Option #3 - Registered via builder passed in through init method.
* @RestInit
* public void init(RestContext.Builder builder ) throws Exception {
* builder .converters(MyConverter.class );
* }
* }
*
*
* Notes:
* -
* When defined as a class, the implementation must have one of the following constructors:
*
* public T(BeanContext)
* public T()
* public static T create (RestContext)
* public static T create ()
*
* -
* Inner classes of the REST resource class are allowed.
*
*
* See Also:
* - {@link Traversable} - Allows URL additional path info to address individual elements in a POJO tree.
*
- {@link Queryable} - Allows query/view/sort functions to be performed on POJOs.
*
- {@link Introspectable} - Allows Java public methods to be invoked on the returned POJOs.
*
- {@link Rest#converters()}
*
- Converters
*
*
* @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 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 bean from: @RestInject(methodScope="foo") public [static] RestConverterList xxx()
beanStore
.createMethodFinder(RestConverterList.class)
.addBean(RestConverterList.Builder.class, v.get())
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// guards
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the guard list sub-builder.
*
* @return The guard list sub-builder.
*/
public RestGuardList.Builder guards() {
if (guards == null)
guards = createGuards(beanStore(), resource());
return guards;
}
/**
* Adds one or more guards to this operation.
*
*
* Equivalent to calling:
*
* builder .guards().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
@SafeVarargs
public final Builder guards(Class extends RestGuard>...value) {
guards().append(value);
return this;
}
/**
* Adds one or more guards to this operation.
*
*
* Equivalent to calling:
*
* builder .guards().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
public Builder guards(RestGuard...value) {
guards().append(value);
return this;
}
/**
* Instantiates the guard list sub-builder.
*
*
* Instantiates based on the following logic:
*
* - Looks for guards set via any of the following:
*
* - {@link RestOpContext.Builder#guards()}}
*
- {@link RestOp#guards()}.
*
- {@link Rest#guards()}.
*
* - Looks for a static or non-static
createGuards() method that returns {@link RestGuard}[] on the
* resource class with any of the following arguments:
*
* - {@link Method} - The Java method this context belongs to.
*
- {@link RestContext}
*
- {@link BeanStore}
*
- Any injected beans.
*
* - Resolves it via the bean store registered in this context.
*
- Instantiates a
RestGuard[0] .
*
*
* @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 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 bean from: @RestInject(methodScope="foo") public [static] RestGuardList xxx()
beanStore
.createMethodFinder(RestGuardList.class)
.addBean(RestGuardList.Builder.class, v.get())
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
RestGuardList getGuards() {
RestGuardList.Builder b = guards();
Set roleGuard = optional(this.roleGuard).orElseGet(CollectionUtils::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 RestMatcherList.Builder matchers() {
if (matchers == null)
matchers = createMatchers(beanStore(), resource());
return matchers;
}
/**
* Adds one or more matchers to this operation.
*
*
* Equivalent to calling:
*
* builder .matchers().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
@SafeVarargs
public final Builder matchers(Class extends RestMatcher>...value) {
matchers().append(value);
return this;
}
/**
* Adds one or more matchers to this operation.
*
*
* Equivalent to calling:
*
* builder .matchers().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
public Builder matchers(RestMatcher...value) {
matchers().append(value);
return this;
}
/**
* Instantiates the matcher list sub-builder.
*
*
* Associates one or more {@link RestMatcher RestMatchers} with the specified method.
*
*
* If multiple matchers are specified, ONE matcher must pass.
*
Note that this is different than guards where ALL guards needs to pass.
*
*
Notes:
* -
* 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:
* - {@link RestOp#matchers()}
*
- {@link RestGet#matchers()}
*
- {@link RestPut#matchers()}
*
- {@link RestPost#matchers()}
*
- {@link RestDelete#matchers()}
*
*
*
* Instantiates based on the following logic:
*
* - Looks for matchers set via any of the following:
*
* - {@link RestOp#matchers()}.
*
* - Looks for a static or non-static
createMatchers() method that returns {@link RestMatcher}[] on the
* resource class with any of the following arguments:
*
* - {@link java.lang.reflect.Method} - The Java method this context belongs to.
*
- {@link RestContext}
*
- {@link BeanStore}
*
- Any injected beans.
*
* - Resolves it via the bean store registered in this context.
*
- Instantiates a
RestMatcher[0] .
*
*
* @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 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 bean from: @RestInject(methodScope="foo") public [static] RestMatcherList xxx()
beanStore
.createMethodFinder(RestMatcherList.class)
.addBean(RestMatcherList.Builder.class, v.get())
.find(this::matches)
.run(x -> v.get().impl(x));
return v.get();
}
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 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 _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));
}
// Replace with bean from: @RestInject(methodScope="foo") public [static] UrlPathMatcherList xxx()
beanStore
.createMethodFinder(UrlPathMatcherList.class, resource().get())
.addBean(UrlPathMatcherList.class, v.get())
.find(this::matches)
.run(x -> v.set(x));
return v.get();
}
/**
* When enabled, append "/*" to path patterns if not already present.
*
* @return This object.
*/
public Builder dotAll() {
dotAll = true;
return this;
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestHeaders
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request headers.
*
* @return The default request headers.
*/
public HeaderList defaultRequestHeaders() {
if (defaultRequestHeaders == null)
defaultRequestHeaders = createDefaultRequestHeaders(beanStore(), parent, resource());
return defaultRequestHeaders;
}
/**
* Adds one or more default request headers to this operation.
*
*
* Equivalent to calling:
*
* builder .defaultRequestHeaders().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
public Builder defaultRequestHeaders(org.apache.http.Header...value) {
defaultRequestHeaders().append(value);
return this;
}
/**
* Instantiates the default request headers.
*
* @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 createDefaultRequestHeaders(BeanStore beanStore, RestContext.Builder parent, Supplier> resource) {
Value v = Value.of(
parent.defaultRequestHeaders().copy()
);
// Replace with bean from: @RestInject(name="defaultRequestHeaders",methodScope="foo") public [static] HeaderList xxx()
BeanStore
.of(beanStore, resource)
.addBean(HeaderList.class, v.get())
.createMethodFinder(HeaderList.class, resource)
.find(x -> matches(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(), parent, resource());
return defaultResponseHeaders;
}
/**
* Adds one or more default response headers to this operation.
*
*
* Equivalent to calling:
*
* builder .defaultResponseHeaders().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
public Builder defaultResponseHeaders(org.apache.http.Header...value) {
defaultResponseHeaders().append(value);
return this;
}
/**
* Instantiates the default response headers.
*
* @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 createDefaultResponseHeaders(BeanStore beanStore, RestContext.Builder parent, Supplier> resource) {
Value v = Value.of(
parent.defaultResponseHeaders().copy()
);
// Replace with bean from: @RestInject(name="defaultResponseHeaders",methodScope="foo") public [static] HeaderList xxx()
BeanStore
.of(beanStore, resource)
.addBean(HeaderList.class, v.get())
.createMethodFinder(HeaderList.class, resource)
.find(x -> matches(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(), parent, resource());
return defaultRequestAttributes;
}
/**
* Adds one or more default request attributes to this operation.
*
*
* Equivalent to calling:
*
* builder .defaultRequestAttributes().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
public Builder defaultRequestAttributes(NamedAttribute...value) {
defaultRequestAttributes().add(value);
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 NamedAttributeMap createDefaultRequestAttributes(BeanStore beanStore, RestContext.Builder parent, Supplier> resource) {
Value v = Value.of(
parent.defaultRequestAttributes().copy()
);
// Replace with bean from: @RestInject(name="defaultRequestAttributes",methodScope="foo") public [static] NamedAttributeMap xxx()
BeanStore
.of(beanStore, resource)
.addBean(NamedAttributeMap.class, v.get())
.createMethodFinder(NamedAttributeMap.class, resource)
.find(x -> matches(x, "defaultRequestAttributes"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestQuery
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request query data.
*
* @return The default request query data.
*/
public PartList defaultRequestQueryData() {
if (defaultRequestQueryData == null)
defaultRequestQueryData = createDefaultRequestQueryData(beanStore(), parent, resource());
return defaultRequestQueryData;
}
/**
* Adds one or more default request query data to this operation.
*
*
* Equivalent to calling:
*
* builder .defaultRequestQueryData().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
public Builder defaultRequestQueryData(NameValuePair...value) {
defaultRequestQueryData().append(value);
return this;
}
/**
* Instantiates the default request query data.
*
* @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 createDefaultRequestQueryData(BeanStore beanStore, RestContext.Builder parent, Supplier> resource) {
Value v = Value.of(
PartList.create()
);
// Replace with bean from: @RestInject(name="defaultRequestQueryData",methodScope="foo") public [static] PartList xxx()
BeanStore
.of(beanStore, resource)
.addBean(PartList.class, v.get())
.createMethodFinder(PartList.class, resource)
.find(x -> matches(x, "defaultRequestQueryData"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// defaultRequestFormData
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the default request form data.
*
* @return The default request form data.
*/
public PartList defaultRequestFormData() {
if (defaultRequestFormData == null)
defaultRequestFormData = createDefaultRequestFormData(beanStore(), parent, resource());
return defaultRequestFormData;
}
/**
* Adds one or more default request form data to this operation.
*
*
* Equivalent to calling:
*
* builder .defaultRequestFormData().append(value );
*
*
* @param value The values to add.
* @return This object.
*/
public Builder defaultRequestFormData(NameValuePair...value) {
defaultRequestFormData().append(value);
return this;
}
/**
* Instantiates the default request form data.
*
* @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 createDefaultRequestFormData(BeanStore beanStore, RestContext.Builder parent, Supplier> resource) {
Value v = Value.of(
PartList.create()
);
// Replace with bean from: @RestInject(name="defaultRequestFormData",methodScope="foo") public [static] PartList xxx()
BeanStore
.of(beanStore, resource)
.addBean(PartList.class, v.get())
.createMethodFinder(PartList.class, resource)
.find(x -> matches(x, "defaultRequestFormData"))
.run(x -> v.set(x));
return v.get();
}
//-----------------------------------------------------------------------------------------------------------------
// Parameter annotations
//-----------------------------------------------------------------------------------------------------------------
/**
* Handles processing of any annotations on parameters.
*
*
* 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
//----------------------------------------------------------------------------------------------------
/**
* Client version pattern matcher.
*
*
* Specifies whether this method can be called based on the client version.
*
*
* The client version is identified via the HTTP request header identified by
* {@link Rest#clientVersionHeader() @Rest(clientVersionHeader)} which by default is "Client-Version" .
*
*
* 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.
*
*
* The format of the client version range is similar to that of OSGi versions.
*
*
* In the following example, the Java methods are mapped to the same HTTP method and URL "/foobar" .
*
* // 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() {...}
*
*
*
* It's common to combine the client version with transforms that will convert new POJOs into older POJOs for
* backwards compatibility.
*
* // Call this method if Client-Version is at least 2.0.
* @RestGet (path="/foobar" , clientVersion="2.0" )
* public NewPojo newMethod() {...}
*
* // Call this method if Client-Version is at least 1.1, but less than 2.0.
* @RestGet (path="/foobar" , clientVersion="[1.1,2.0)" )
* @BeanConfig(swaps=NewToOldSwap.class )
* public NewPojo oldMethod() {
* return newMethod();
* }
*
*
* 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:
*
* "[0,1.0)" = Less than 1.0. 1.0 and 1.0.0 does not match.
* "[0,1.0]" = Less than or equal to 1.0. Note that 1.0.1 will match.
* "1.0" = At least 1.0. 1.0 and 2.0 will match.
*
*
* See Also:
* - {@link RestOp#clientVersion}
*
- {@link RestGet#clientVersion}
*
- {@link RestPut#clientVersion}
*
- {@link RestPost#clientVersion}
*
- {@link RestDelete#clientVersion}
*
- {@link RestContext.Builder#clientVersionHeader(String)}
*
*
* @param value The new value for this setting.
* @return This object.
*/
@FluentSetter
public Builder clientVersion(String value) {
clientVersion = value;
return this;
}
/**
* Debug mode.
*
*
* Enables the following:
*
* -
* HTTP request/response bodies are cached in memory for logging purposes.
*
*
*
* 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.
*
*
* The default character encoding for the request and response if not specified on the request.
*
*
* This overrides the value defined on the {@link RestContext}.
*
*
See Also:
* - {@link RestContext.Builder#defaultCharset(Charset)}
*
- {@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;
}
/**
* HTTP method name.
*
*
* Typically "GET" , "PUT" , "POST" , "DELETE" , or "OPTIONS" .
*
*
* Method names are case-insensitive (always folded to upper-case).
*
*
* Note that you can use {@link org.apache.juneau.http.HttpMethod} for constant values.
*
*
* Besides the standard HTTP method names, the following can also be specified:
*
* -
*
"*"
* - Denotes any method.
*
Use this if you want to capture any HTTP methods in a single Java method.
*
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.
* -
*
""
* - Auto-detect.
*
The method name is determined based on the Java method name.
*
For example, if the method is doPost(...) , then the method name is automatically detected
* as "POST" .
*
Otherwise, defaults to "GET" .
* -
*
"RRPC"
* - Remote-proxy interface.
*
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
* RestClient.getRemoteInterface(Class<T> interfaceClass, String url) .
*
This allows you to construct client-side interface proxies using REST as a transport medium.
*
Conceptually, this is simply a fancy POST against the url "/{path}/{javaMethodName}"
* 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.
* -
* Anything else
* - Overloaded non-HTTP-standard names that are passed in through a
&method=methodName URL
* parameter.
*
*
* See Also:
* - {@link RestOp#method()}
*
- {@link RestGet}
*
- {@link RestPut}
*
- {@link RestPost}
*
- {@link RestDelete}
*
*
* @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.
*
*
* 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;
}
/**
* Resource method paths.
*
*
* Identifies the URL subpath relative to the servlet class.
*
*
*
Notes:
* -
* This method is only applicable for Java methods.
*
-
* 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.
*
*
* @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.
*
*
* 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.
*
*
* 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;
}
/**
* Declared roles.
*
*
* A comma-delimited list of all possible user roles.
*
*
* Used in conjunction with {@link RestOpContext.Builder#roleGuard(String)} is used with patterns.
*
*
Example:
*
* @Rest (
* rolesDeclared="ROLE_ADMIN,ROLE_READ_WRITE,ROLE_READ_ONLY,ROLE_SPECIAL" ,
* roleGuard="ROLE_ADMIN || (ROLE_READ_WRITE && ROLE_SPECIAL)"
* )
* public class MyResource extends BasicRestServlet {
* ...
* }
*
*
* See Also:
* - {@link Rest#rolesDeclared}
*
*
* @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.
*
*
* An expression defining if a user with the specified roles are allowed to access methods on this class.
*
*
Example:
*
* @Rest (
* path="/foo" ,
* roleGuard="ROLE_ADMIN || (ROLE_READ_WRITE && ROLE_SPECIAL)"
* )
* public class MyResource extends BasicRestServlet {
* ...
* }
*
*
* Notes:
* -
* Supports any of the following expression constructs:
*
* "foo" - Single arguments.
* "foo,bar,baz" - Multiple OR'ed arguments.
* "foo | bar | bqz" - Multiple OR'ed arguments, pipe syntax.
* "foo || bar || bqz" - Multiple OR'ed arguments, Java-OR syntax.
* "fo*" - Patterns including '*' and '?' .
* "fo* & *oo" - Multiple AND'ed arguments, ampersand syntax.
* "fo* && *oo" - Multiple AND'ed arguments, Java-AND syntax.
* "fo* || (*oo || bar)" - Parenthesis.
*
* -
* AND operations take precedence over OR operations (as expected).
*
-
* Whitespace is ignored.
*
-
*
null or empty expressions always match as false .
* -
* If patterns are used, you must specify the list of declared roles using {@link Rest#rolesDeclared()} or {@link RestOpContext.Builder#rolesDeclared(String...)}.
*
-
* Supports SVL Variables
* (e.g.
"$L{my.localized.variable}" ).
*
*
* @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.
*
*
* 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.
*
*
* 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;
}
//
@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 boolean matches(MethodInfo annotated) {
RestInject a = annotated.getAnnotation(RestInject.class);
if (a != null) {
for (String n : a.methodScope()) {
if ("*".equals(n) || restMethod.getName().equals(n))
return true;
}
}
return false;
}
private boolean matches(MethodInfo annotated, String beanName) {
RestInject a = annotated.getAnnotation(RestInject.class);
if (a != null) {
if (! a.name().equals(beanName))
return false;
for (String n : a.methodScope()) {
if ("*".equals(n) || restMethod.getName().equals(n))
return true;
}
}
return false;
}
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 NamedAttributeMap defaultRequestAttributes;
private final Charset defaultCharset;
private final long maxInput;
private final List
supportedAcceptTypes,
supportedContentTypes;
private final CallLogger callLogger;
private final Map,ResponseBeanMeta> responseBeanMetas = new ConcurrentHashMap<>();
private final Map,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.getBeanStore()).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();
defaultResponseHeaders = builder.defaultResponseHeaders();
defaultRequestQueryData = builder.defaultRequestQueryData();
defaultRequestFormData = builder.defaultRequestFormData();
defaultRequestAttributes = builder.defaultRequestAttributes();
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 null 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 null 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. "GET" ).
*
* @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: partSerializer .
*
* @return The value of the partSerializer property on this bean, or null if it is not set.
*/
public HttpPartSerializer getPartSerializer() {
return partSerializer;
}
/**
* Bean property getter: partParser .
*
* @return The value of the partParser property on this bean, or null 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 null .
*/
public HeaderList getDefaultRequestHeaders() {
return defaultRequestHeaders;
}
/**
* Returns the default response headers.
*
* @return The default response headers. Never null .
*/
public HeaderList getDefaultResponseHeaders() {
return defaultResponseHeaders;
}
/**
* Returns the default request query parameters.
*
* @return The default request query parameters. Never null .
*/
public PartList getDefaultRequestQueryData() {
return defaultRequestQueryData;
}
/**
* Returns the default form data parameters.
*
* @return The default form data parameters. Never null .
*/
public PartList getDefaultRequestFormData() {
return defaultRequestFormData;
}
/**
* Returns the default request attributes.
*
* @return The default request attributes. Never null .
*/
public NamedAttributeMap getDefaultRequestAttributes() {
return defaultRequestAttributes;
}
/**
* Returns the default charset.
*
* @return The default charset. Never null .
*/
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 getSupportedContentTypes() {
return supportedContentTypes;
}
/**
* Returns a list of supported accept types.
*
* @return An unmodifiable list.
*/
public List getSupportedAcceptTypes() {
return supportedAcceptTypes;
}
/**
* Returns the response bean meta if this method returns a {@link Response}-annotated bean.
*
* @return The response bean meta or null if it's not a {@link Response}-annotated bean.
*/
public ResponseBeanMeta getResponseMeta() {
return responseMeta;
}
/**
* Identifies if this method can process the specified call.
*
*
* To process the call, the following must be true:
*
* - Path pattern must match.
*
- Matchers (if any) must match.
*
*
* @param session The call to check.
* @return
* One of the following values:
*
* 0 - Path doesn't match.
* 1 - Path matched but matchers did not.
* 2 - Matches.
*
*/
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;
}
}