This is an automated email from the ASF dual-hosted git repository. wujimin pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git
The following commit(s) were added to refs/heads/master by this push: new 5507eec [SCB-1961] add rest server codec filter 5507eec is described below commit 5507eec39888b5eedf0f4452dcdb6574787a7db8 Author: wujimin <wuji...@huawei.com> AuthorDate: Sat May 30 16:20:13 2020 +0800 [SCB-1961] add rest server codec filter --- .../common/rest/AbstractRestInvocation.java | 14 +- .../common/rest/filter/RestFilterProvider.java | 32 ++- .../rest/filter/inner/RestServerCodecFilter.java | 130 ++++++++++++ .../rest/filter/inner/ServerRestArgsFilter.java | 15 +- ...g.apache.servicecomb.core.filter.FilterProvider | 18 ++ .../rest/RestProducerInvocationCreatorTest.java | 4 +- .../filter/inner/RestServerCodecFilterTest.java | 221 +++++++++++++++++++++ .../apache/servicecomb/core/filter/FilterMeta.java | 11 + .../{FilterMeta.java => FilterProvider.java} | 22 +- 9 files changed, 403 insertions(+), 64 deletions(-) diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java index 92bac2f..f0bc2ca 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java @@ -21,7 +21,6 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import javax.ws.rs.core.HttpHeaders; @@ -33,6 +32,7 @@ import org.apache.servicecomb.common.rest.codec.produce.ProduceProcessorManager; import org.apache.servicecomb.common.rest.definition.RestOperationMeta; import org.apache.servicecomb.common.rest.filter.HttpServerFilter; import org.apache.servicecomb.common.rest.filter.HttpServerFilterBeforeSendResponseExecutor; +import org.apache.servicecomb.common.rest.filter.inner.RestServerCodecFilter; import org.apache.servicecomb.common.rest.locator.OperationLocator; import org.apache.servicecomb.common.rest.locator.ServicePathManager; import org.apache.servicecomb.core.Const; @@ -268,16 +268,8 @@ public abstract class AbstractRestInvocation { @SuppressWarnings("deprecation") protected void sendResponse(Response response) { - if (response.getHeaders().getHeaderMap() != null) { - for (Entry<String, List<Object>> entry : response.getHeaders().getHeaderMap().entrySet()) { - for (Object value : entry.getValue()) { - if (!entry.getKey().equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH) - && !entry.getKey().equalsIgnoreCase("Transfer-Encoding")) { - responseEx.addHeader(entry.getKey(), String.valueOf(value)); - } - } - } - } + RestServerCodecFilter.copyHeadersToHttpResponse(response.getHeaders().getHeaderMap(), responseEx); + responseEx.setStatus(response.getStatusCode(), response.getReasonPhrase()); responseEx.setAttribute(RestConst.INVOCATION_HANDLER_RESPONSE, response); responseEx.setAttribute(RestConst.INVOCATION_HANDLER_PROCESSOR, produceProcessor); diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/RestFilterProvider.java similarity index 60% copy from core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java copy to common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/RestFilterProvider.java index 680ba45..b91a96e 100644 --- a/core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/RestFilterProvider.java @@ -14,26 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.apache.servicecomb.common.rest.filter; -package org.apache.servicecomb.core.filter; +import java.util.Arrays; +import java.util.List; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import org.apache.servicecomb.common.rest.filter.inner.RestServerCodecFilter; +import org.apache.servicecomb.core.filter.Filter; +import org.apache.servicecomb.core.filter.FilterProvider; -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -public @interface FilterMeta { - String name(); - - /** - * - * @return true to use same instance for all filter chains - */ - boolean shareable() default true; +public class RestFilterProvider implements FilterProvider { + @Override + public List<Class<? extends Filter>> getFilters() { + return Arrays.asList( + RestServerCodecFilter.class + ); + } } diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java new file mode 100644 index 0000000..7842302 --- /dev/null +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java @@ -0,0 +1,130 @@ +/* + * 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.servicecomb.common.rest.filter.inner; + +import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; +import static com.google.common.net.HttpHeaders.TRANSFER_ENCODING; +import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static org.apache.servicecomb.core.exception.Exceptions.exceptionToResponse; +import static org.apache.servicecomb.swagger.invocation.InvocationType.PRODUCER; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; + +import javax.servlet.http.Part; + +import org.apache.servicecomb.common.rest.HttpTransportContext; +import org.apache.servicecomb.common.rest.RestConst; +import org.apache.servicecomb.common.rest.codec.RestCodec; +import org.apache.servicecomb.common.rest.codec.produce.ProduceProcessor; +import org.apache.servicecomb.common.rest.definition.RestOperationMeta; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.definition.OperationMeta; +import org.apache.servicecomb.core.filter.Filter; +import org.apache.servicecomb.core.filter.FilterMeta; +import org.apache.servicecomb.core.filter.FilterNode; +import org.apache.servicecomb.foundation.common.utils.AsyncUtils; +import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; +import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; +import org.apache.servicecomb.foundation.vertx.stream.BufferOutputStream; +import org.apache.servicecomb.swagger.invocation.Response; + +import io.netty.buffer.Unpooled; + +@FilterMeta(name = "rest-server-codec", invocationType = PRODUCER) +public class RestServerCodecFilter implements Filter { + @Override + public CompletableFuture<Response> onFilter(Invocation invocation, FilterNode nextNode) { + return CompletableFuture.completedFuture(invocation) + .thenCompose(this::decodeRequest) + .thenCompose(nextNode::onFilter) + .exceptionally(exception -> exceptionToResponse(invocation, exception, INTERNAL_SERVER_ERROR)) + .thenCompose(response -> encodeResponse(invocation, response)); + } + + private CompletableFuture<Invocation> decodeRequest(Invocation invocation) { + HttpTransportContext transportContext = invocation.getTransportContext(); + HttpServletRequestEx requestEx = transportContext.getRequestEx(); + + OperationMeta operationMeta = invocation.getOperationMeta(); + RestOperationMeta restOperationMeta = operationMeta.getExtData(RestConst.SWAGGER_REST_OPERATION); + Map<String, Object> swaggerArguments = RestCodec.restToArgs(requestEx, restOperationMeta); + invocation.setSwaggerArguments(swaggerArguments); + + return CompletableFuture.completedFuture(invocation); + } + + private CompletableFuture<Response> encodeResponse(Invocation invocation, Response response) { + HttpTransportContext transportContext = invocation.getTransportContext(); + ProduceProcessor produceProcessor = transportContext.getProduceProcessor(); + HttpServletResponseEx responseEx = transportContext.getResponseEx(); + boolean download = isDownloadFileResponseType(invocation, response); + + return encodeResponse(response, download, produceProcessor, responseEx); + } + + @SuppressWarnings("deprecation") + public static CompletableFuture<Response> encodeResponse(Response response, boolean download, + ProduceProcessor produceProcessor, HttpServletResponseEx responseEx) { + responseEx.setStatus(response.getStatusCode(), response.getReasonPhrase()); + copyHeadersToHttpResponse(response.getHeaders().getHeaderMap(), responseEx); + + if (download) { + return CompletableFuture.completedFuture(response); + } + + responseEx.setContentType(produceProcessor.getName() + "; charset=utf-8"); + try (BufferOutputStream output = new BufferOutputStream(Unpooled.compositeBuffer())) { + produceProcessor.encodeResponse(output, response.getResult()); + + responseEx.setBodyBuffer(output.getBuffer()); + + return CompletableFuture.completedFuture(response); + } catch (Throwable e) { + return AsyncUtils.completeExceptionally(e); + } + } + + /** + * Check whether this response is a downloaded file response, + * according to the schema recorded in {@link org.apache.servicecomb.swagger.invocation.response.ResponsesMeta} + * and response status code. + * @return true if this response is a downloaded file, otherwise false. + */ + public static boolean isDownloadFileResponseType(Invocation invocation, Response response) { + return Part.class.isAssignableFrom( + invocation.findResponseType(response.getStatusCode()).getRawClass()); + } + + public static void copyHeadersToHttpResponse(Map<String, List<Object>> headerMap, HttpServletResponseEx responseEx) { + if (headerMap == null) { + return; + } + + for (Entry<String, List<Object>> entry : headerMap.entrySet()) { + for (Object value : entry.getValue()) { + if (!entry.getKey().equalsIgnoreCase(CONTENT_LENGTH) + && !entry.getKey().equalsIgnoreCase(TRANSFER_ENCODING)) { + responseEx.addHeader(entry.getKey(), String.valueOf(value)); + } + } + } + } +} diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ServerRestArgsFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ServerRestArgsFilter.java index 4043cb4..63ae8ee 100644 --- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ServerRestArgsFilter.java +++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/ServerRestArgsFilter.java @@ -17,11 +17,11 @@ package org.apache.servicecomb.common.rest.filter.inner; +import static org.apache.servicecomb.common.rest.filter.inner.RestServerCodecFilter.isDownloadFileResponseType; + import java.util.Map; import java.util.concurrent.CompletableFuture; -import javax.servlet.http.Part; - import org.apache.servicecomb.common.rest.RestConst; import org.apache.servicecomb.common.rest.codec.RestCodec; import org.apache.servicecomb.common.rest.codec.produce.ProduceProcessor; @@ -91,15 +91,4 @@ public class ServerRestArgsFilter implements HttpServerFilter { } return future; } - - /** - * Check whether this response is a downloaded file response, - * according to the schema recorded in {@link org.apache.servicecomb.swagger.invocation.response.ResponsesMeta} - * and response status code. - * @return true if this response is a downloaded file, otherwise false. - */ - private boolean isDownloadFileResponseType(Invocation invocation, Response response) { - return Part.class.isAssignableFrom( - invocation.findResponseType(response.getStatusCode()).getRawClass()); - } } diff --git a/common/common-rest/src/main/resources/META-INF/services/org.apache.servicecomb.core.filter.FilterProvider b/common/common-rest/src/main/resources/META-INF/services/org.apache.servicecomb.core.filter.FilterProvider new file mode 100644 index 0000000..cf4b594 --- /dev/null +++ b/common/common-rest/src/main/resources/META-INF/services/org.apache.servicecomb.core.filter.FilterProvider @@ -0,0 +1,18 @@ +# +# 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. +# + +org.apache.servicecomb.common.rest.filter.RestFilterProvider \ No newline at end of file diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreatorTest.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreatorTest.java index fd4b72a..a79b1fc 100644 --- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreatorTest.java +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/RestProducerInvocationCreatorTest.java @@ -78,7 +78,7 @@ public class RestProducerInvocationCreatorTest { static SCBEngine engine; @BeforeClass - public static void beforeClass() throws Exception { + public static void beforeClass() { ArchaiusUtils.resetConfig(); ConfigUtil.installDynamicConfig(); @@ -87,7 +87,7 @@ public class RestProducerInvocationCreatorTest { } @AfterClass - public static void afterClass() throws Exception { + public static void afterClass() { engine.destroy(); ArchaiusUtils.resetConfig(); } diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilterTest.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilterTest.java new file mode 100644 index 0000000..a937d51 --- /dev/null +++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilterTest.java @@ -0,0 +1,221 @@ +/* + * 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.servicecomb.common.rest.filter.inner; + +import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; +import static com.google.common.net.HttpHeaders.TRANSFER_ENCODING; +import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.apache.servicecomb.common.rest.HttpTransportContext; +import org.apache.servicecomb.common.rest.RestConst; +import org.apache.servicecomb.common.rest.definition.RestOperationMeta; +import org.apache.servicecomb.config.ConfigUtil; +import org.apache.servicecomb.core.Endpoint; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.SCBEngine; +import org.apache.servicecomb.core.SCBStatus; +import org.apache.servicecomb.core.bootstrap.SCBBootstrap; +import org.apache.servicecomb.core.definition.OperationMeta; +import org.apache.servicecomb.core.filter.FilterNode; +import org.apache.servicecomb.core.invocation.InvocationFactory; +import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils; +import org.apache.servicecomb.foundation.test.scaffolding.exception.RuntimeExceptionWithoutStackTrace; +import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; +import org.apache.servicecomb.swagger.invocation.Response; +import org.apache.servicecomb.swagger.invocation.response.Headers; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.fasterxml.jackson.databind.type.TypeFactory; + +import io.vertx.core.json.Json; +import mockit.Expectations; +import mockit.Mocked; +import mockit.Verifications; + +public class RestServerCodecFilterTest { + RestServerCodecFilter codecFilter = new RestServerCodecFilter(); + + Invocation invocation; + + @Mocked + Endpoint endpoint; + + @Mocked + OperationMeta operationMeta; + + @Mocked + RestOperationMeta restOperationMeta; + + @Mocked + HttpTransportContext transportContext; + + @Mocked + HttpServletResponseEx responseEx; + + Headers headers = new Headers(); + + FilterNode nextNode = new FilterNode((invocation, next) -> { + Response response = Response.ok("ok"); + response.setHeaders(headers); + return CompletableFuture.completedFuture(response); + }); + + static SCBEngine engine; + + @BeforeClass + public static void beforeClass() { + ArchaiusUtils.resetConfig(); + ConfigUtil.installDynamicConfig(); + + engine = SCBBootstrap.createSCBEngineForTest(); + engine.setStatus(SCBStatus.UP); + } + + @AfterClass + public static void afterClass() { + engine.destroy(); + ArchaiusUtils.resetConfig(); + } + + @Before + public void setUp() { + invocation = InvocationFactory.forProvider(endpoint, operationMeta, null); + } + + private void mockDecodeRequestFail() { + new Expectations(invocation) { + { + invocation.getTransportContext(); + result = transportContext; + + transportContext.getRequestEx(); + result = new RuntimeExceptionWithoutStackTrace("encode request failed"); + } + }; + } + + @Test + public void should_not_invoke_filter_when_decode_request_failed(@Mocked FilterNode nextNode) { + mockDecodeRequestFail(); + + codecFilter.onFilter(invocation, nextNode); + + new Verifications() { + { + nextNode.onFilter(invocation); + times = 0; + } + }; + } + + @Test + public void should_convert_exception_to_response_when_decode_request_failed() + throws ExecutionException, InterruptedException { + mockDecodeRequestFail(); + new Expectations(invocation) { + { + invocation.findResponseType(anyInt); + result = TypeFactory.defaultInstance().constructType(String.class); + } + }; + + Response response = codecFilter.onFilter(invocation, nextNode).get(); + + assertThat(response.getStatus()).isEqualTo(INTERNAL_SERVER_ERROR); + assertThat(Json.encode(response.getResult())) + .isEqualTo("{\"code\":\"SCB.5000\",\"message\":\"encode request failed\"}"); + } + + private void success_invocation() throws InterruptedException, ExecutionException { + new Expectations(invocation) { + { + invocation.getTransportContext(); + result = transportContext; + + operationMeta.getExtData(RestConst.SWAGGER_REST_OPERATION); + result = restOperationMeta; + + invocation.findResponseType(anyInt); + result = TypeFactory.defaultInstance().constructType(String.class); + } + }; + + codecFilter.onFilter(invocation, nextNode).get(); + } + + @Test + public void should_encode_response_header() throws ExecutionException, InterruptedException { + headers.addHeader("h1", "v1"); + success_invocation(); + + new Verifications() { + { + String name; + String value; + responseEx.addHeader(name = withCapture(), value = withCapture()); + assertThat(name).isEqualTo("h1"); + assertThat(value).isEqualTo("v1"); + } + }; + } + + @Test + public void should_not_encode_content_length_header() throws ExecutionException, InterruptedException { + headers.addHeader("h1", "v1") + .addHeader("h2", "v2") + .addHeader(CONTENT_LENGTH, 10); + success_invocation(); + + new Verifications() { + { + List<String> names = new ArrayList<>(); + List<String> values = new ArrayList<>(); + responseEx.addHeader(withCapture(names), withCapture(values)); + assertThat(names).containsExactly("h1", "h2"); + assertThat(values).containsExactly("v1", "v2"); + } + }; + } + + @Test + public void should_not_encode_transfer_encoding_header() throws ExecutionException, InterruptedException { + headers.addHeader("h1", "v1") + .addHeader("h2", "v2") + .addHeader(TRANSFER_ENCODING, "test"); + success_invocation(); + + new Verifications() { + { + List<String> names = new ArrayList<>(); + List<String> values = new ArrayList<>(); + responseEx.addHeader(withCapture(names), withCapture(values)); + assertThat(names).containsExactly("h1", "h2"); + assertThat(values).containsExactly("v1", "v2"); + } + }; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java b/core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java index 680ba45..e40aca4 100644 --- a/core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java +++ b/core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java @@ -17,6 +17,9 @@ package org.apache.servicecomb.core.filter; +import static org.apache.servicecomb.swagger.invocation.InvocationType.CONSUMER; +import static org.apache.servicecomb.swagger.invocation.InvocationType.PRODUCER; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -24,6 +27,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.apache.servicecomb.swagger.invocation.InvocationType; + @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @@ -33,6 +38,12 @@ public @interface FilterMeta { /** * + * @return can be used for the specific invocation type + */ + InvocationType[] invocationType() default {CONSUMER, PRODUCER}; + + /** + * * @return true to use same instance for all filter chains */ boolean shareable() default true; diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java b/core/src/main/java/org/apache/servicecomb/core/filter/FilterProvider.java similarity index 63% copy from core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java copy to core/src/main/java/org/apache/servicecomb/core/filter/FilterProvider.java index 680ba45..df14364 100644 --- a/core/src/main/java/org/apache/servicecomb/core/filter/FilterMeta.java +++ b/core/src/main/java/org/apache/servicecomb/core/filter/FilterProvider.java @@ -14,26 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.servicecomb.core.filter; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Inherited -public @interface FilterMeta { - String name(); +import java.util.List; - /** - * - * @return true to use same instance for all filter chains - */ - boolean shareable() default true; +public interface FilterProvider { + List<Class<? extends Filter>> getFilters(); }