You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2020/06/02 03:42:28 UTC
[servicecomb-java-chassis] branch master updated: [SCB-1968] add
producer operation filter
This is an automated email from the ASF dual-hosted git repository.
liubao 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 16b60ea [SCB-1968] add producer operation filter
16b60ea is described below
commit 16b60ea5fd445e894e55b6bb7e0a793a4c406fa9
Author: wujimin <wu...@huawei.com>
AuthorDate: Mon Jun 1 21:20:42 2020 +0800
[SCB-1968] add producer operation filter
---
.../IllegalArgumentExceptionConverter.java | 49 ++++++
.../core/filter/impl/DefaultFilterProvider.java | 3 +-
.../core/filter/impl/ProducerOperationFilter.java | 94 ++++++++++
...e.servicecomb.core.exception.ExceptionConverter | 1 +
.../servicecomb/core/filter/FilterChainTest.java | 11 +-
.../filter/impl/ProducerOperationFilterTest.java | 193 +++++++++++++++++++++
6 files changed, 344 insertions(+), 7 deletions(-)
diff --git a/core/src/main/java/org/apache/servicecomb/core/exception/converter/IllegalArgumentExceptionConverter.java b/core/src/main/java/org/apache/servicecomb/core/exception/converter/IllegalArgumentExceptionConverter.java
new file mode 100644
index 0000000..2e3ccb9
--- /dev/null
+++ b/core/src/main/java/org/apache/servicecomb/core/exception/converter/IllegalArgumentExceptionConverter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.core.exception.converter;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.core.Response.StatusType;
+
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.core.exception.ExceptionConverter;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class IllegalArgumentExceptionConverter implements ExceptionConverter<IllegalArgumentException> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(IllegalArgumentExceptionConverter.class);
+
+ @Override
+ public int getOrder() {
+ return Short.MAX_VALUE;
+ }
+
+ @Override
+ public boolean canConvert(Throwable throwable) {
+ return throwable instanceof IllegalArgumentException;
+ }
+
+ @Override
+ public InvocationException convert(@Nullable Invocation invocation, IllegalArgumentException throwable,
+ StatusType genericStatus) {
+ LOGGER.error("convert IllegalArgumentException exception to InvocationException.", throwable);
+
+ return new InvocationException(genericStatus, ExceptionConverter.getGenericCode(genericStatus),
+ "Parameters not valid or types not match.", throwable);
+ }
+}
diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/impl/DefaultFilterProvider.java b/core/src/main/java/org/apache/servicecomb/core/filter/impl/DefaultFilterProvider.java
index e2a2054..ab4e4ac 100644
--- a/core/src/main/java/org/apache/servicecomb/core/filter/impl/DefaultFilterProvider.java
+++ b/core/src/main/java/org/apache/servicecomb/core/filter/impl/DefaultFilterProvider.java
@@ -27,6 +27,7 @@ public class DefaultFilterProvider implements FilterProvider {
public List<Class<? extends Filter>> getFilters() {
return Arrays.asList(
SimpleLoadBalanceFilter.class,
- ScheduleFilter.class);
+ ScheduleFilter.class,
+ ProducerOperationFilter.class);
}
}
diff --git a/core/src/main/java/org/apache/servicecomb/core/filter/impl/ProducerOperationFilter.java b/core/src/main/java/org/apache/servicecomb/core/filter/impl/ProducerOperationFilter.java
new file mode 100644
index 0000000..8819522
--- /dev/null
+++ b/core/src/main/java/org/apache/servicecomb/core/filter/impl/ProducerOperationFilter.java
@@ -0,0 +1,94 @@
+/*
+ * 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.core.filter.impl;
+
+import static org.apache.servicecomb.swagger.invocation.InvocationType.PRODUCER;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.core.exception.Exceptions;
+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.swagger.engine.SwaggerProducerOperation;
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.context.ContextUtils;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@FilterMeta(name = "producer-operation", invocationType = PRODUCER)
+public class ProducerOperationFilter implements Filter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ProducerOperationFilter.class);
+
+ @Override
+ public CompletableFuture<Response> onFilter(Invocation invocation, FilterNode nextNode) {
+ invocation.onBusinessMethodStart();
+
+ SwaggerProducerOperation producerOperation = invocation.getOperationMeta().getSwaggerProducerOperation();
+ Object instance = producerOperation.getProducerInstance();
+ Method method = producerOperation.getProducerMethod();
+ Object[] args = invocation.toProducerArguments();
+ return invoke(invocation, instance, method, args)
+ .thenApply(result -> convertResultToResponse(invocation, producerOperation, result))
+ .whenComplete((response, throwable) -> whenComplete(invocation, throwable));
+ }
+
+ @SuppressWarnings("unchecked")
+ private CompletableFuture<Object> invoke(Invocation invocation, Object instance, Method method, Object[] args) {
+ ContextUtils.setInvocationContext(invocation);
+
+ try {
+ Object result = method.invoke(instance, args);
+ if (result instanceof CompletableFuture) {
+ return (CompletableFuture<Object>) result;
+ }
+
+ return CompletableFuture.completedFuture(result);
+ } catch (Throwable e) {
+ return AsyncUtils.completeExceptionally(Exceptions.unwrap(e));
+ } finally {
+ ContextUtils.removeInvocationContext();
+ }
+ }
+
+ private Response convertResultToResponse(Invocation invocation, SwaggerProducerOperation producerOperation,
+ Object result) {
+ return producerOperation.getResponseMapper().mapResponse(invocation.getStatus(), result);
+ }
+
+ private void whenComplete(Invocation invocation, Throwable throwable) {
+ if (shouldPrintErrorLog(throwable)) {
+ LOGGER.error("unexpected error {},", invocation.getInvocationQualifiedName(), throwable);
+ }
+
+ invocation.onBusinessMethodFinish();
+ invocation.onBusinessFinish();
+ }
+
+ protected boolean shouldPrintErrorLog(Throwable throwable) {
+ if (throwable == null) {
+ return false;
+ }
+
+ Throwable unwrapped = Exceptions.unwrap(throwable);
+ return !(unwrapped instanceof InvocationException);
+ }
+}
diff --git a/core/src/main/resources/META-INF/services/org.apache.servicecomb.core.exception.ExceptionConverter b/core/src/main/resources/META-INF/services/org.apache.servicecomb.core.exception.ExceptionConverter
index 798c7d1..f129ed1 100644
--- a/core/src/main/resources/META-INF/services/org.apache.servicecomb.core.exception.ExceptionConverter
+++ b/core/src/main/resources/META-INF/services/org.apache.servicecomb.core.exception.ExceptionConverter
@@ -16,4 +16,5 @@
#
org.apache.servicecomb.core.exception.converter.InvocationExceptionConverter
+org.apache.servicecomb.core.exception.converter.IllegalArgumentExceptionConverter
org.apache.servicecomb.core.exception.converter.DefaultExceptionConverter
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/servicecomb/core/filter/FilterChainTest.java b/core/src/test/java/org/apache/servicecomb/core/filter/FilterChainTest.java
index a7d75c1..d184fa4 100644
--- a/core/src/test/java/org/apache/servicecomb/core/filter/FilterChainTest.java
+++ b/core/src/test/java/org/apache/servicecomb/core/filter/FilterChainTest.java
@@ -139,14 +139,13 @@ public class FilterChainTest {
};
SimpleRetryFilter retryFilter = new SimpleRetryFilter().setMaxRetry(3);
- ExecutionException executionException = (ExecutionException) catchThrowable(
- () -> FilterNode.buildChain(retryFilter, recordThreadFilter, exceptionFilter)
- .onFilter(invocation)
- .get());
+ CompletableFuture<Response> future = FilterNode.buildChain(retryFilter, recordThreadFilter, exceptionFilter)
+ .onFilter(invocation);
assertThat(msg).containsExactly("main", "main", "main");
- assertThat(executionException.getCause())
- .isInstanceOf(IOException.class)
+ assertThat(future)
+ .hasFailedWithThrowableThat()
+ .isExactlyInstanceOf(IOException.class)
.hasMessage("net error");
}
}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/servicecomb/core/filter/impl/ProducerOperationFilterTest.java b/core/src/test/java/org/apache/servicecomb/core/filter/impl/ProducerOperationFilterTest.java
new file mode 100644
index 0000000..0ed0c4b
--- /dev/null
+++ b/core/src/test/java/org/apache/servicecomb/core/filter/impl/ProducerOperationFilterTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.core.filter.impl;
+
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.core.exception.Exceptions;
+import org.apache.servicecomb.core.filter.FilterNode;
+import org.apache.servicecomb.foundation.test.scaffolding.exception.RuntimeExceptionWithoutStackTrace;
+import org.apache.servicecomb.swagger.engine.SwaggerProducerOperation;
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.context.ContextUtils;
+import org.apache.servicecomb.swagger.invocation.context.InvocationContext;
+import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+import org.junit.Test;
+
+import mockit.Expectations;
+import mockit.Injectable;
+import mockit.Mocked;
+import mockit.Verifications;
+
+public class ProducerOperationFilterTest {
+ ProducerOperationFilter filter = new ProducerOperationFilter();
+
+ @Injectable
+ Invocation invocation;
+
+ @Mocked
+ SwaggerProducerOperation producerOperation;
+
+ static InvocationContext threadInvocationContext;
+
+ public static class Controller {
+ public void sync() {
+ threadInvocationContext = ContextUtils.getInvocationContext();
+ }
+
+ public void syncException() {
+ throw new RuntimeExceptionWithoutStackTrace("syncException");
+ }
+
+ public CompletableFuture<Void> async() {
+ threadInvocationContext = ContextUtils.getInvocationContext();
+ return CompletableFuture.completedFuture(null);
+ }
+
+ public CompletableFuture<Void> asyncException() {
+ throw new RuntimeExceptionWithoutStackTrace("asyncException");
+ }
+ }
+
+ @Test
+ public void should_record_invocation_trace_time() {
+ filter.onFilter(invocation, FilterNode.EMPTY);
+
+ new Verifications() {
+ {
+ invocation.onBusinessMethodStart();
+ times = 1;
+
+ invocation.onBusinessMethodFinish();
+ times = 1;
+
+ invocation.onBusinessFinish();
+ times = 1;
+ }
+ };
+ }
+
+ private void setInvokeMethod(String name) throws NoSuchMethodException {
+ Controller instance = new Controller();
+ Method method = instance.getClass().getMethod(name);
+ new Expectations() {
+ {
+ producerOperation.getProducerInstance();
+ result = instance;
+
+ producerOperation.getProducerMethod();
+ result = method;
+ }
+ };
+ }
+
+ private void setInvokeSyncMethod() throws NoSuchMethodException {
+ setInvokeMethod("sync");
+ }
+
+ private void setInvokeAsyncMethod() throws NoSuchMethodException {
+ setInvokeMethod("async");
+ }
+
+ @Test
+ public void should_provide_thread_local_invocation_context_for_sync_method() throws NoSuchMethodException {
+ setInvokeSyncMethod();
+
+ filter.onFilter(invocation, FilterNode.EMPTY);
+
+ assertThat(threadInvocationContext).isSameAs(invocation);
+ }
+
+ @Test
+ public void should_clear_thread_local_invocation_context_after_sync_method() throws NoSuchMethodException {
+ setInvokeSyncMethod();
+
+ filter.onFilter(invocation, FilterNode.EMPTY);
+
+ assertThat(ContextUtils.getInvocationContext()).isNull();
+ }
+
+ @Test
+ public void should_catch_sync_business_exception() throws NoSuchMethodException {
+ setInvokeMethod("syncException");
+
+ CompletableFuture<Response> future = filter.onFilter(invocation, FilterNode.EMPTY);
+
+ assertThat(future).hasFailedWithThrowableThat()
+ .isInstanceOf(RuntimeExceptionWithoutStackTrace.class)
+ .hasMessage("syncException");
+ }
+
+ @Test
+ public void should_provide_thread_local_invocation_context_for_async_method() throws NoSuchMethodException {
+ setInvokeAsyncMethod();
+
+ filter.onFilter(invocation, FilterNode.EMPTY);
+
+ assertThat(threadInvocationContext).isSameAs(invocation);
+ }
+
+ @Test
+ public void should_clear_thread_local_invocation_context_after_async_method() throws NoSuchMethodException {
+ setInvokeAsyncMethod();
+
+ filter.onFilter(invocation, FilterNode.EMPTY);
+
+ assertThat(ContextUtils.getInvocationContext()).isNull();
+ }
+
+ @Test
+ public void should_catch_async_business_exception() throws NoSuchMethodException {
+ setInvokeMethod("asyncException");
+
+ CompletableFuture<Response> future = filter.onFilter(invocation, FilterNode.EMPTY);
+
+ assertThat(future).hasFailedWithThrowableThat()
+ .isInstanceOf(RuntimeExceptionWithoutStackTrace.class)
+ .hasMessage("asyncException");
+ }
+
+ @Test
+ public void should_unify_IllegalArgumentException_message_when_convert_exception() throws NoSuchMethodException {
+ setInvokeSyncMethod();
+ new Expectations() {
+ {
+ invocation.toProducerArguments();
+ result = new Object[] {1};
+ }
+ };
+
+ CompletableFuture<Response> future = filter.onFilter(invocation, FilterNode.EMPTY);
+ assertThat(future).hasFailedWithThrowableThat()
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("wrong number of arguments");
+
+ InvocationException throwable = Exceptions
+ .convert(invocation, catchThrowable(() -> future.get()), INTERNAL_SERVER_ERROR);
+ assertThat(throwable).hasCauseInstanceOf(IllegalArgumentException.class);
+ CommonExceptionData data = (CommonExceptionData) throwable.getErrorData();
+ assertThat(data.getMessage()).isEqualTo("Parameters not valid or types not match.");
+ }
+}
\ No newline at end of file