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

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:
*/ 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 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...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...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...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 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 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 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...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()} *
* * @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...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...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 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 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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy